@rubytech/taskmaster 1.9.1 → 1.9.3

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.
@@ -1,15 +1,18 @@
1
1
  import { createSubsystemLogger } from "../../logging/subsystem.js";
2
- import { fetchWithTimeout, normalizeBaseUrl, readErrorResponse } from "../../media-understanding/providers/shared.js";
2
+ import { fetchWithTimeout, normalizeBaseUrl, readErrorResponse, } from "../../media-understanding/providers/shared.js";
3
3
  const log = createSubsystemLogger("image-gen");
4
+ /** Show first 4 and last 4 characters of a key, mask everything in between. */
5
+ function maskKey(key) {
6
+ if (key.length <= 12)
7
+ return `${key.slice(0, 4)}...`;
8
+ return `${key.slice(0, 4)}...${key.slice(-4)}`;
9
+ }
4
10
  /* ------------------------------------------------------------------ */
5
11
  /* Constants */
6
12
  /* ------------------------------------------------------------------ */
7
13
  const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
8
14
  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
- ]);
15
+ const GEMINI_IMAGE_MODELS = new Set(["gemini-2.5-flash-image", "gemini-3-pro-image-preview"]);
13
16
  /* ------------------------------------------------------------------ */
14
17
  /* Model detection */
15
18
  /* ------------------------------------------------------------------ */
@@ -47,7 +50,7 @@ export async function generateImageGemini(params) {
47
50
  const res = await fetchWithTimeout(url, { method: "POST", headers, body: JSON.stringify(body) }, timeoutMs, fetchFn);
48
51
  if (!res.ok) {
49
52
  const detail = await readErrorResponse(res);
50
- log.error("gemini HTTP error", { status: res.status, detail });
53
+ log.error("gemini HTTP error", { status: res.status, key: maskKey(params.apiKey), detail });
51
54
  const suffix = detail ? `: ${detail}` : "";
52
55
  throw new Error(`Image generation failed (HTTP ${res.status})${suffix}`);
53
56
  }
@@ -125,7 +128,7 @@ export async function generateImageImagen(params) {
125
128
  const res = await fetchWithTimeout(url, { method: "POST", headers, body: JSON.stringify(body) }, timeoutMs, fetchFn);
126
129
  if (!res.ok) {
127
130
  const detail = await readErrorResponse(res);
128
- log.error("imagen HTTP error", { status: res.status, detail });
131
+ log.error("imagen HTTP error", { status: res.status, key: maskKey(params.apiKey), detail });
129
132
  const suffix = detail ? `: ${detail}` : "";
130
133
  throw new Error(`Image generation failed (HTTP ${res.status})${suffix}`);
131
134
  }
@@ -8,6 +8,15 @@ import { sanitizeToolResultImages } from "../tool-images.js";
8
8
  import { readNumberParam, readStringParam } from "./common.js";
9
9
  const log = createSubsystemLogger("image-gen");
10
10
  /* ------------------------------------------------------------------ */
11
+ /* Helpers */
12
+ /* ------------------------------------------------------------------ */
13
+ /** Show first 4 and last 4 characters of a key, mask everything in between. */
14
+ function maskKey(key) {
15
+ if (key.length <= 12)
16
+ return `${key.slice(0, 4)}...`;
17
+ return `${key.slice(0, 4)}...${key.slice(-4)}`;
18
+ }
19
+ /* ------------------------------------------------------------------ */
11
20
  /* Constants */
12
21
  /* ------------------------------------------------------------------ */
13
22
  const SUPPORTED_MODELS = new Set([
@@ -47,7 +56,9 @@ export function createImageGenerateTool(options) {
47
56
  parameters: Type.Object({
48
57
  prompt: Type.String({ description: "The image generation prompt." }),
49
58
  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." })),
59
+ aspectRatio: Type.Optional(Type.String({
60
+ 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.",
61
+ })),
51
62
  resolution: Type.Optional(Type.String({ description: "1K, 2K, or 4K (4K: gemini-3-pro-image-preview only)." })),
52
63
  numberOfImages: Type.Optional(Type.Number({ description: "1-4 images per request. Imagen models only." })),
53
64
  personGeneration: Type.Optional(Type.String({ description: "dont_allow, allow_adult, allow_all. Imagen models only." })),
@@ -73,6 +84,7 @@ export function createImageGenerateTool(options) {
73
84
  agentDir: options?.agentDir,
74
85
  });
75
86
  const apiKey = requireApiKey(auth, "google");
87
+ log.info("resolved google API key", { source: auth.source, key: maskKey(apiKey) });
76
88
  /* --- Call the appropriate backend ---------------------------- */
77
89
  const isGemini = isGeminiImageModel(modelRaw);
78
90
  const result = isGemini
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.9.1",
3
- "commit": "0b919997a5bc45f672df09785fe6c44edd03fdc0",
4
- "builtAt": "2026-02-27T06:34:25.149Z"
2
+ "version": "1.9.3",
3
+ "commit": "65d408bbe013355d2df19762fe1a745a98c3584e",
4
+ "builtAt": "2026-02-27T07:59:49.321Z"
5
5
  }
@@ -267,6 +267,34 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_3 = [
267
267
  changes.push("Set default model fallbacks: google/gemini-3-pro-preview, openai/gpt-5.2.");
268
268
  },
269
269
  },
270
+ {
271
+ id: "admin-agent-tools-add-image_generate",
272
+ describe: "Add image_generate to admin agent tools.allow",
273
+ apply: (raw, changes) => {
274
+ const agents = getRecord(raw.agents);
275
+ const list = getAgentsList(agents);
276
+ for (const entry of list) {
277
+ if (!isRecord(entry))
278
+ continue;
279
+ const id = typeof entry.id === "string" ? entry.id.trim() : "";
280
+ if (!id)
281
+ continue;
282
+ const isAdmin = id === "admin" || id.endsWith("-admin");
283
+ if (!isAdmin)
284
+ continue;
285
+ const tools = getRecord(entry.tools);
286
+ if (!tools)
287
+ continue;
288
+ if (!Array.isArray(tools.allow))
289
+ continue;
290
+ const allow = tools.allow;
291
+ if (!allow.includes("image_generate")) {
292
+ allow.push("image_generate");
293
+ changes.push(`Added image_generate to agent "${id}" tools.allow.`);
294
+ }
295
+ }
296
+ },
297
+ },
270
298
  {
271
299
  id: "agents-tools-remove-sessions_send",
272
300
  describe: "Remove sessions_send from agent tools.allow (tool removed for security)",
@@ -102,21 +102,16 @@ export async function startGatewayServer(port = 18789, opts = {}) {
102
102
  .join("\n")}`);
103
103
  }
104
104
  }
105
- // Persist default model fallbacks to config if missing. The in-memory
106
- // defaults cover runtime, but writing to disk makes the fallback chain
107
- // visible in the config file and survives across gateway restarts.
105
+ // Run all migrations unconditionally to catch additive changes (new tools
106
+ // in agent allow lists, new defaults) that the legacy-rules system cannot
107
+ // detect rules flag keys that *exist*, not keys that are *missing*.
108
+ // Migrations are idempotent; nothing is written when there are no changes.
108
109
  if (configSnapshot.exists && !isNixMode) {
109
110
  const parsed = configSnapshot.parsed;
110
- const agentsRaw = parsed?.agents;
111
- const defaultsRaw = agentsRaw?.defaults;
112
- const modelRaw = defaultsRaw?.model;
113
- const fallbacks = modelRaw?.fallbacks;
114
- if (!Array.isArray(fallbacks) || fallbacks.length === 0) {
115
- const { config: migrated, changes } = migrateLegacyConfig(parsed ?? {});
116
- if (migrated && changes.length > 0) {
117
- await writeConfigFile(migrated);
118
- log.info(`gateway: applied config defaults:\n${changes.map((entry) => `- ${entry}`).join("\n")}`);
119
- }
111
+ const { config: migrated, changes } = migrateLegacyConfig(parsed ?? {});
112
+ if (migrated && changes.length > 0) {
113
+ await writeConfigFile(migrated);
114
+ log.info(`gateway: applied config defaults:\n${changes.map((entry) => `- ${entry}`).join("\n")}`);
120
115
  }
121
116
  }
122
117
  configSnapshot = await readConfigFileSnapshot();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/taskmaster",
3
- "version": "1.9.1",
3
+ "version": "1.9.3",
4
4
  "description": "AI-powered business assistant for small businesses",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -1356,7 +1356,7 @@ Or, if the SD card is already written:
1356
1356
  ssh admin@taskmaster.local
1357
1357
  ```
1358
1358
 
1359
- Enter the password when prompted. The default password depends on how the Pi was set up — typically `taskmaster` for pre-installed devices, or whatever you chose during Raspberry Pi OS setup.
1359
+ Enter the password when prompted. The default password is `password` for pre-installed devices, or whatever you chose during Raspberry Pi OS setup.
1360
1360
 
1361
1361
  > **Security tip:** Change the default password after your first SSH login by running `passwd` on the Pi.
1362
1362