@rubytech/taskmaster 1.0.98 → 1.0.100

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 (52) hide show
  1. package/README.md +1 -1
  2. package/dist/agents/system-prompt.js +2 -1
  3. package/dist/agents/taskmaster-tools.js +6 -0
  4. package/dist/agents/tool-policy.js +2 -0
  5. package/dist/agents/tools/image-generate-api.js +154 -0
  6. package/dist/agents/tools/image-generate-tool.js +145 -0
  7. package/dist/build-info.json +3 -3
  8. package/dist/control-ui/assets/{index-TI7yF6r_.js → index-BiXCzgVk.js} +244 -244
  9. package/dist/control-ui/assets/index-BiXCzgVk.js.map +1 -0
  10. package/dist/control-ui/assets/{index-6WdtDXJj.css → index-Bj8TaDNH.css} +1 -1
  11. package/dist/control-ui/index.html +2 -2
  12. package/dist/gateway/chat-sanitize.js +59 -4
  13. package/dist/gateway/control-ui.js +8 -7
  14. package/dist/gateway/server-methods/files.js +3 -3
  15. package/dist/hooks/bundled/license-request/HOOK.md +47 -0
  16. package/dist/hooks/bundled/license-request/handler.js +192 -0
  17. package/package.json +1 -1
  18. package/scripts/install.sh +2 -2
  19. package/skills/image-gen/SKILL.md +68 -0
  20. package/skills/image-gen/references/models.md +83 -0
  21. package/skills/image-gen/references/prompting.md +184 -0
  22. package/skills/image-gen/references/styles.md +113 -0
  23. package/skills/image-gen/references/troubleshooting.md +93 -0
  24. package/skills/taskmaster/SKILL.md +6 -6
  25. package/taskmaster-docs/USER-GUIDE.md +67 -6
  26. package/templates/beagle/agents/admin/AGENTS.md +2 -2
  27. package/templates/beagle/agents/public/AGENTS.md +2 -2
  28. package/templates/beagle/skills/beagle/SKILL.md +3 -3
  29. package/templates/beagle/skills/beagle/references/booking-schema.md +1 -1
  30. package/templates/beagle/skills/beagle/references/data-compliance.md +2 -2
  31. package/templates/beagle/skills/beagle/references/fee-collection.md +1 -1
  32. package/templates/beagle/skills/beagle/references/workflow.md +2 -2
  33. package/templates/maxy/TOOLS.md +15 -0
  34. package/templates/maxy/agents/admin/AGENTS.md +70 -0
  35. package/templates/maxy/agents/admin/BOOTSTRAP.md +30 -0
  36. package/templates/maxy/agents/admin/HEARTBEAT.md +6 -0
  37. package/templates/maxy/agents/admin/IDENTITY.md +13 -0
  38. package/templates/maxy/agents/admin/SOUL.md +21 -0
  39. package/templates/maxy/agents/admin/TOOLS.md +20 -0
  40. package/templates/maxy/agents/admin/USER.md +17 -0
  41. package/templates/maxy/agents/public/AGENTS.md +72 -0
  42. package/templates/maxy/agents/public/HEARTBEAT.md +2 -0
  43. package/templates/maxy/agents/public/IDENTITY.md +13 -0
  44. package/templates/maxy/agents/public/SOUL.md +60 -0
  45. package/templates/maxy/agents/public/TOOLS.md +20 -0
  46. package/templates/maxy/agents/public/USER.md +17 -0
  47. package/templates/maxy/memory/public/FAQ.md +241 -0
  48. package/templates/maxy/skills/maxy/SKILL.md +55 -0
  49. package/templates/maxy/skills/personal-assistant/SKILL.md +50 -0
  50. package/templates/taskmaster/agents/admin/AGENTS.md +20 -0
  51. package/templates/taskmaster/agents/public/AGENTS.md +9 -0
  52. package/dist/control-ui/assets/index-TI7yF6r_.js.map +0 -1
package/README.md CHANGED
@@ -22,7 +22,7 @@ taskmaster provision
22
22
  Or one command on a fresh device:
23
23
 
24
24
  ```bash
25
- curl -fsSL https://taskmaster.bot/install.sh | bash
25
+ curl -fsSL https://taskmaster.bot/install.sh | sudo bash
26
26
  ```
27
27
 
28
28
  After install, open the setup wizard in your browser:
@@ -8,10 +8,11 @@ function buildSkillsSection(params) {
8
8
  return [];
9
9
  return [
10
10
  "## Skills (mandatory)",
11
- "Before replying: scan <available_skills> <description> entries.",
11
+ "Before taking any action (including tool calls): scan <available_skills> <description> entries.",
12
12
  `- If exactly one skill clearly applies: read its SKILL.md at <location> with \`${params.readToolName}\`, then follow it.`,
13
13
  "- If multiple could apply: choose the most specific one, then read/follow it.",
14
14
  "- If none clearly apply: do not read any SKILL.md.",
15
+ "Skills encode expertise that improves output quality. Skipping them produces worse results even when the task seems simple.",
15
16
  "Constraints: never read more than one skill up front; only read after selecting.",
16
17
  trimmed,
17
18
  "",
@@ -27,6 +27,7 @@ import { createContactUpdateTool } from "./tools/contact-update-tool.js";
27
27
  import { createRelayMessageTool } from "./tools/relay-message-tool.js";
28
28
  import { createSkillReadTool } from "./tools/skill-read-tool.js";
29
29
  import { createApiKeysTool } from "./tools/apikeys-tool.js";
30
+ import { createImageGenerateTool } from "./tools/image-generate-tool.js";
30
31
  export function createTaskmasterTools(options) {
31
32
  const imageTool = options?.agentDir?.trim()
32
33
  ? createImageTool({
@@ -121,6 +122,11 @@ export function createTaskmasterTools(options) {
121
122
  ...(webSearchTool ? [webSearchTool] : []),
122
123
  ...(webFetchTool ? [webFetchTool] : []),
123
124
  ...(imageTool ? [imageTool] : []),
125
+ createImageGenerateTool({
126
+ config: options?.config,
127
+ agentDir: options?.agentDir,
128
+ workspaceDir: options?.workspaceDir,
129
+ }),
124
130
  ...(documentTool ? [documentTool] : []),
125
131
  createCurrentTimeTool({ config: options?.config }),
126
132
  createAuthorizeAdminTool(),
@@ -60,6 +60,7 @@ export const TOOL_GROUPS = {
60
60
  "web_search",
61
61
  "web_fetch",
62
62
  "image",
63
+ "image_generate",
63
64
  "current_time",
64
65
  "authorize_admin",
65
66
  "revoke_admin",
@@ -116,6 +117,7 @@ const TOOL_PROFILES = {
116
117
  "contact_update",
117
118
  "relay_message",
118
119
  "memory_save_media",
120
+ "image_generate",
119
121
  ],
120
122
  deny: [...PROFILE_NEVER_GRANT],
121
123
  },
@@ -0,0 +1,154 @@
1
+ import { createSubsystemLogger } from "../../logging/subsystem.js";
2
+ import { fetchWithTimeout, normalizeBaseUrl, readErrorResponse } from "../../media-understanding/providers/shared.js";
3
+ const log = createSubsystemLogger("image-gen");
4
+ /* ------------------------------------------------------------------ */
5
+ /* Constants */
6
+ /* ------------------------------------------------------------------ */
7
+ const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
8
+ const DEFAULT_TIMEOUT_MS = 120_000;
9
+ const GEMINI_IMAGE_MODELS = new Set([
10
+ "gemini-2.5-flash-image",
11
+ "gemini-3-pro-image-preview",
12
+ ]);
13
+ /* ------------------------------------------------------------------ */
14
+ /* Model detection */
15
+ /* ------------------------------------------------------------------ */
16
+ export function isGeminiImageModel(model) {
17
+ return GEMINI_IMAGE_MODELS.has(model);
18
+ }
19
+ /* ------------------------------------------------------------------ */
20
+ /* Gemini native image generation (:generateContent) */
21
+ /* ------------------------------------------------------------------ */
22
+ export async function generateImageGemini(params) {
23
+ const fetchFn = params.fetchFn ?? fetch;
24
+ const baseUrl = normalizeBaseUrl(params.baseUrl, DEFAULT_BASE_URL);
25
+ const timeoutMs = params.timeoutMs ?? DEFAULT_TIMEOUT_MS;
26
+ const url = `${baseUrl}/models/${params.model}:generateContent`;
27
+ const headers = new Headers();
28
+ headers.set("content-type", "application/json");
29
+ headers.set("x-goog-api-key", params.apiKey);
30
+ /* Build generationConfig — imageConfig only when there is something to set */
31
+ const generationConfig = {
32
+ responseModalities: ["text", "image"],
33
+ };
34
+ const imageConfig = {};
35
+ if (params.aspectRatio)
36
+ imageConfig.aspectRatio = params.aspectRatio;
37
+ if (params.resolution)
38
+ imageConfig.imageSize = params.resolution;
39
+ if (Object.keys(imageConfig).length > 0) {
40
+ generationConfig.imageConfig = imageConfig;
41
+ }
42
+ const body = {
43
+ contents: [{ parts: [{ text: params.prompt }] }],
44
+ generationConfig,
45
+ };
46
+ log.info("gemini request", { url, model: params.model, body: JSON.stringify(body) });
47
+ const res = await fetchWithTimeout(url, { method: "POST", headers, body: JSON.stringify(body) }, timeoutMs, fetchFn);
48
+ if (!res.ok) {
49
+ const detail = await readErrorResponse(res);
50
+ log.error("gemini HTTP error", { status: res.status, detail });
51
+ const suffix = detail ? `: ${detail}` : "";
52
+ throw new Error(`Image generation failed (HTTP ${res.status})${suffix}`);
53
+ }
54
+ // Parse and log response structure (truncate base64 to avoid log explosion)
55
+ const rawPayload = await res.json();
56
+ const payloadForLog = JSON.stringify(rawPayload, (_key, value) => {
57
+ if (typeof value === "string" && value.length > 200)
58
+ return `${value.slice(0, 200)}...[${value.length} chars]`;
59
+ return value;
60
+ });
61
+ log.info("gemini response", { payload: payloadForLog });
62
+ const payload = rawPayload;
63
+ const parts = payload.candidates?.[0]?.content?.parts ?? [];
64
+ // Log the keys present in each part so we can see the actual field names
65
+ log.info("gemini response parts", {
66
+ candidateCount: payload.candidates?.length ?? 0,
67
+ partCount: parts.length,
68
+ partKeys: parts.map((p) => Object.keys(p)),
69
+ });
70
+ const images = [];
71
+ const textParts = [];
72
+ for (const part of parts) {
73
+ // Check both snake_case (REST) and camelCase (SDK) field names
74
+ const inlineData = (part.inline_data ?? part.inlineData);
75
+ if (inlineData?.data) {
76
+ images.push({
77
+ base64: inlineData.data,
78
+ mimeType: inlineData.mime_type ?? inlineData.mimeType ?? "image/png",
79
+ });
80
+ }
81
+ else if (typeof part.text === "string" && part.text.trim()) {
82
+ textParts.push(part.text.trim());
83
+ }
84
+ }
85
+ if (images.length === 0) {
86
+ log.error("gemini: no images found in response", {
87
+ partCount: parts.length,
88
+ partKeys: parts.map((p) => Object.keys(p)),
89
+ });
90
+ throw new Error("Image generation returned no images");
91
+ }
92
+ const result = { images, model: params.model };
93
+ if (textParts.length > 0) {
94
+ result.text = textParts.join("\n");
95
+ }
96
+ return result;
97
+ }
98
+ /* ------------------------------------------------------------------ */
99
+ /* Imagen 4 image generation (:predict) */
100
+ /* ------------------------------------------------------------------ */
101
+ export async function generateImageImagen(params) {
102
+ const fetchFn = params.fetchFn ?? fetch;
103
+ const baseUrl = normalizeBaseUrl(params.baseUrl, DEFAULT_BASE_URL);
104
+ const timeoutMs = params.timeoutMs ?? DEFAULT_TIMEOUT_MS;
105
+ const url = `${baseUrl}/models/${params.model}:predict`;
106
+ const headers = new Headers();
107
+ headers.set("content-type", "application/json");
108
+ headers.set("x-goog-api-key", params.apiKey);
109
+ /* Only include parameters that are actually provided */
110
+ const parameters = {};
111
+ if (params.aspectRatio)
112
+ parameters.aspectRatio = params.aspectRatio;
113
+ if (params.resolution)
114
+ parameters.imageSize = params.resolution;
115
+ if (params.personGeneration)
116
+ parameters.personGeneration = params.personGeneration;
117
+ if (params.sampleCount !== undefined && params.sampleCount >= 1) {
118
+ parameters.sampleCount = params.sampleCount;
119
+ }
120
+ const body = {
121
+ instances: [{ prompt: params.prompt }],
122
+ parameters,
123
+ };
124
+ log.info("imagen request", { url, model: params.model, body: JSON.stringify(body) });
125
+ const res = await fetchWithTimeout(url, { method: "POST", headers, body: JSON.stringify(body) }, timeoutMs, fetchFn);
126
+ if (!res.ok) {
127
+ const detail = await readErrorResponse(res);
128
+ log.error("imagen HTTP error", { status: res.status, detail });
129
+ const suffix = detail ? `: ${detail}` : "";
130
+ throw new Error(`Image generation failed (HTTP ${res.status})${suffix}`);
131
+ }
132
+ const rawPayload = await res.json();
133
+ const payloadForLog = JSON.stringify(rawPayload, (_key, value) => {
134
+ if (typeof value === "string" && value.length > 200)
135
+ return `${value.slice(0, 200)}...[${value.length} chars]`;
136
+ return value;
137
+ });
138
+ log.info("imagen response", { payload: payloadForLog });
139
+ const payload = rawPayload;
140
+ const predictions = payload.predictions ?? [];
141
+ const images = [];
142
+ for (const pred of predictions) {
143
+ if (pred.bytesBase64Encoded && pred.mimeType) {
144
+ images.push({
145
+ base64: pred.bytesBase64Encoded,
146
+ mimeType: pred.mimeType,
147
+ });
148
+ }
149
+ }
150
+ if (images.length === 0) {
151
+ throw new Error("Image generation returned no images");
152
+ }
153
+ return { images, model: params.model };
154
+ }
@@ -0,0 +1,145 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { Type } from "@sinclair/typebox";
4
+ import { requireApiKey, resolveApiKeyForProvider } from "../model-auth.js";
5
+ import { generateImageGemini, generateImageImagen, isGeminiImageModel, } from "./image-generate-api.js";
6
+ import { createSubsystemLogger } from "../../logging/subsystem.js";
7
+ import { sanitizeToolResultImages } from "../tool-images.js";
8
+ import { readNumberParam, readStringParam } from "./common.js";
9
+ const log = createSubsystemLogger("image-gen");
10
+ /* ------------------------------------------------------------------ */
11
+ /* Constants */
12
+ /* ------------------------------------------------------------------ */
13
+ const SUPPORTED_MODELS = new Set([
14
+ "gemini-2.5-flash-image",
15
+ "gemini-3-pro-image-preview",
16
+ "imagen-4.0-generate-001",
17
+ "imagen-4.0-ultra-generate-001",
18
+ "imagen-4.0-fast-generate-001",
19
+ ]);
20
+ const DEFAULT_MODEL = "gemini-2.5-flash-image";
21
+ /* ------------------------------------------------------------------ */
22
+ /* Helpers */
23
+ /* ------------------------------------------------------------------ */
24
+ function slugify(text, maxLen = 40) {
25
+ return text
26
+ .toLowerCase()
27
+ .replace(/[^a-z0-9]+/g, "-")
28
+ .replace(/^-+|-+$/g, "")
29
+ .slice(0, maxLen);
30
+ }
31
+ function extensionForMime(mimeType) {
32
+ return mimeType.includes("jpeg") ? "jpg" : "png";
33
+ }
34
+ /* ------------------------------------------------------------------ */
35
+ /* Tool factory */
36
+ /* ------------------------------------------------------------------ */
37
+ export function createImageGenerateTool(options) {
38
+ log.info("image_generate tool registered", { workspaceDir: options?.workspaceDir });
39
+ return {
40
+ label: "Image Generate",
41
+ name: "image_generate",
42
+ description: "Generate images using Google AI. Image quality depends heavily on model choice and prompt engineering — " +
43
+ "the image-gen skill provides expert guidance for both. " +
44
+ "Gemini models: gemini-2.5-flash-image (fast), gemini-3-pro-image-preview (pro, 4K, text rendering). " +
45
+ "Imagen models: imagen-4.0-generate-001, imagen-4.0-ultra-generate-001, imagen-4.0-fast-generate-001. " +
46
+ "numberOfImages and personGeneration apply to Imagen models only.",
47
+ parameters: Type.Object({
48
+ prompt: Type.String({ description: "The image generation prompt." }),
49
+ model: Type.Optional(Type.String({ description: "Model ID. Default: gemini-2.5-flash-image." })),
50
+ aspectRatio: Type.Optional(Type.String({ description: "e.g. 1:1, 16:9, 9:16, 3:4, 4:3. Gemini also supports 2:3, 3:2, 4:5, 5:4, 21:9." })),
51
+ resolution: Type.Optional(Type.String({ description: "1K, 2K, or 4K (4K: gemini-3-pro-image-preview only)." })),
52
+ numberOfImages: Type.Optional(Type.Number({ description: "1-4 images per request. Imagen models only." })),
53
+ personGeneration: Type.Optional(Type.String({ description: "dont_allow, allow_adult, allow_all. Imagen models only." })),
54
+ }),
55
+ execute: async (_toolCallId, args) => {
56
+ log.info("image_generate execute called", { toolCallId: _toolCallId, args });
57
+ const params = args && typeof args === "object" ? args : {};
58
+ /* --- Parse parameters ---------------------------------------- */
59
+ const prompt = readStringParam(params, "prompt", { required: true });
60
+ const modelRaw = readStringParam(params, "model") ?? DEFAULT_MODEL;
61
+ const aspectRatio = readStringParam(params, "aspectRatio");
62
+ const resolution = readStringParam(params, "resolution");
63
+ const numberOfImages = readNumberParam(params, "numberOfImages", { integer: true });
64
+ const personGeneration = readStringParam(params, "personGeneration");
65
+ /* --- Validate model ------------------------------------------ */
66
+ if (!SUPPORTED_MODELS.has(modelRaw)) {
67
+ throw new Error(`Unsupported model "${modelRaw}". Supported: ${[...SUPPORTED_MODELS].join(", ")}`);
68
+ }
69
+ /* --- Resolve API key ----------------------------------------- */
70
+ const auth = await resolveApiKeyForProvider({
71
+ provider: "google",
72
+ cfg: options?.config,
73
+ agentDir: options?.agentDir,
74
+ });
75
+ const apiKey = requireApiKey(auth, "google");
76
+ /* --- Call the appropriate backend ---------------------------- */
77
+ const isGemini = isGeminiImageModel(modelRaw);
78
+ const result = isGemini
79
+ ? await generateImageGemini({
80
+ apiKey,
81
+ model: modelRaw,
82
+ prompt,
83
+ aspectRatio,
84
+ resolution,
85
+ })
86
+ : await generateImageImagen({
87
+ apiKey,
88
+ model: modelRaw,
89
+ prompt,
90
+ aspectRatio,
91
+ resolution,
92
+ sampleCount: numberOfImages,
93
+ personGeneration,
94
+ });
95
+ /* --- Save images to disk ------------------------------------ */
96
+ const workspaceDir = options?.workspaceDir?.trim();
97
+ const uploadDir = workspaceDir
98
+ ? path.join(workspaceDir, "uploads", "generated")
99
+ : path.join(process.cwd(), "uploads", "generated");
100
+ await fs.mkdir(uploadDir, { recursive: true });
101
+ const timestamp = Date.now();
102
+ const slug = slugify(prompt);
103
+ const savedPaths = [];
104
+ for (let i = 0; i < result.images.length; i++) {
105
+ const img = result.images[i];
106
+ const ext = extensionForMime(img.mimeType);
107
+ const suffix = result.images.length > 1 ? `-${i + 1}` : "";
108
+ const filename = `${timestamp}-${slug}${suffix}.${ext}`;
109
+ const filePath = path.join(uploadDir, filename);
110
+ await fs.writeFile(filePath, Buffer.from(img.base64, "base64"));
111
+ savedPaths.push(filePath);
112
+ }
113
+ /* --- Return result ------------------------------------------- */
114
+ // Build content with ALL images as base64 blocks plus MEDIA: annotations
115
+ // for the chat sanitizer to convert to /api/media URLs.
116
+ // The text block uses MEDIA: format (not human-readable file paths) so the
117
+ // sanitizer can extract paths and strip the text before display.
118
+ const content = [];
119
+ // One MEDIA: annotation per image (sanitizer converts these to URL blocks)
120
+ const mediaLines = savedPaths.map((p) => `MEDIA:${p}`).join("\n");
121
+ content.push({ type: "text", text: mediaLines });
122
+ // Base64 image blocks for ALL images (sanitizer strips these for bandwidth)
123
+ for (const img of result.images) {
124
+ content.push({
125
+ type: "image",
126
+ data: img.base64,
127
+ mimeType: img.mimeType,
128
+ });
129
+ }
130
+ log.info("image_generate returning", {
131
+ imageCount: result.images.length,
132
+ paths: savedPaths,
133
+ });
134
+ const toolResult = {
135
+ content,
136
+ details: {
137
+ model: result.model,
138
+ prompt,
139
+ paths: savedPaths,
140
+ },
141
+ };
142
+ return await sanitizeToolResultImages(toolResult, "Image Generate");
143
+ },
144
+ };
145
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.0.98",
3
- "commit": "2c46716f6bfa85862ca9241069064d0860ce4f72",
4
- "builtAt": "2026-02-21T19:47:09.017Z"
2
+ "version": "1.0.100",
3
+ "commit": "afd8ec8222bfb6bac4c7e8839b3c78c9e7df2575",
4
+ "builtAt": "2026-02-22T20:15:07.050Z"
5
5
  }