@rubytech/taskmaster 1.24.0 → 1.25.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.
@@ -161,7 +161,7 @@ export function buildSystemPrompt(params) {
161
161
  },
162
162
  });
163
163
  const ttsHint = params.config ? buildTtsSystemPromptHint(params.config) : undefined;
164
- return buildAgentSystemPrompt({
164
+ const prompt = buildAgentSystemPrompt({
165
165
  workspaceDir: params.workspaceDir,
166
166
  defaultThinkLevel: params.defaultThinkLevel,
167
167
  extraSystemPrompt: params.extraSystemPrompt,
@@ -178,6 +178,11 @@ export function buildSystemPrompt(params) {
178
178
  contextFiles: params.contextFiles,
179
179
  ttsHint,
180
180
  });
181
+ return {
182
+ prompt,
183
+ bootstrapFiles: params.bootstrapFiles ?? [],
184
+ skillsPrompt: params.skillsPrompt ?? "",
185
+ };
181
186
  }
182
187
  export function normalizeCliModel(modelId, backend) {
183
188
  const trimmed = modelId.trim();
@@ -10,7 +10,9 @@ import { makeBootstrapWarn, resolveBootstrapContextForRun } from "./bootstrap-fi
10
10
  import { resolveCliBackendConfig } from "./cli-backends.js";
11
11
  import { appendImagePathsToPrompt, buildCliArgs, buildSystemPrompt, cleanupResumeProcesses, cleanupSuspendedCliProcesses, enqueueCliRun, normalizeCliModel, parseCliJson, parseCliJsonl, resolvePromptInput, resolveSessionIdToSend, resolveSystemPromptUsage, writeCliImages, } from "./cli-runner/helpers.js";
12
12
  import { FailoverError, resolveFailoverStatus } from "./failover-error.js";
13
- import { classifyFailoverReason, isFailoverErrorMessage } from "./pi-embedded-helpers.js";
13
+ import { classifyFailoverReason, isFailoverErrorMessage, resolveBootstrapMaxChars, } from "./pi-embedded-helpers.js";
14
+ import { buildSystemPromptReport } from "./system-prompt-report.js";
15
+ import { writeSystemPromptLog } from "./system-prompt-log.js";
14
16
  const log = createSubsystemLogger("agent/claude-cli");
15
17
  export async function runCliAgent(params) {
16
18
  const started = Date.now();
@@ -31,7 +33,7 @@ export async function runCliAgent(params) {
31
33
  .filter(Boolean)
32
34
  .join("\n");
33
35
  const sessionLabel = params.sessionKey ?? params.sessionId;
34
- const { contextFiles } = await resolveBootstrapContextForRun({
36
+ const { bootstrapFiles, contextFiles } = await resolveBootstrapContextForRun({
35
37
  workspaceDir,
36
38
  config: params.config,
37
39
  sessionKey: params.sessionKey,
@@ -51,7 +53,7 @@ export async function runCliAgent(params) {
51
53
  cwd: process.cwd(),
52
54
  moduleUrl: import.meta.url,
53
55
  });
54
- const systemPrompt = buildSystemPrompt({
56
+ const { prompt: systemPrompt, skillsPrompt } = buildSystemPrompt({
55
57
  workspaceDir,
56
58
  config: params.config,
57
59
  defaultThinkLevel: params.thinkLevel,
@@ -63,7 +65,28 @@ export async function runCliAgent(params) {
63
65
  contextFiles,
64
66
  modelDisplay,
65
67
  agentId: sessionAgentId,
68
+ bootstrapFiles,
66
69
  });
70
+ const systemPromptReport = buildSystemPromptReport({
71
+ source: "run",
72
+ generatedAt: Date.now(),
73
+ sessionId: params.sessionId,
74
+ sessionKey: params.sessionKey,
75
+ provider: params.provider,
76
+ model: modelId,
77
+ workspaceDir,
78
+ bootstrapMaxChars: resolveBootstrapMaxChars(params.config),
79
+ systemPrompt,
80
+ bootstrapFiles,
81
+ injectedFiles: contextFiles,
82
+ skillsPrompt,
83
+ tools: [],
84
+ });
85
+ // Fire-and-forget: log full system prompt to disk
86
+ const sessionKeyForLog = params.sessionKey ?? params.sessionId;
87
+ if (sessionKeyForLog) {
88
+ writeSystemPromptLog(sessionKeyForLog, systemPrompt).catch(() => { });
89
+ }
67
90
  const { sessionId: cliSessionIdToSend, isNew } = resolveSessionIdToSend({
68
91
  backend,
69
92
  cliSessionId: params.cliSessionId,
@@ -219,6 +242,7 @@ export async function runCliAgent(params) {
219
242
  model: modelId,
220
243
  usage: output.usage,
221
244
  },
245
+ systemPromptReport,
222
246
  },
223
247
  };
224
248
  }
@@ -32,6 +32,7 @@ import { acquireSessionWriteLock } from "../../session-write-lock.js";
32
32
  import { applySkillEnvOverrides, applySkillEnvOverridesFromSnapshot, loadWorkspaceSkillEntries, resolveSkillsPromptForRun, } from "../../skills.js";
33
33
  import { DEFAULT_BOOTSTRAP_FILENAME } from "../../workspace.js";
34
34
  import { buildSystemPromptReport } from "../../system-prompt-report.js";
35
+ import { writeSystemPromptLog } from "../../system-prompt-log.js";
35
36
  import { resolveDefaultModelForAgent } from "../../model-selection.js";
36
37
  import { isAbortError } from "../abort.js";
37
38
  import { buildEmbeddedExtensionPaths } from "../extensions.js";
@@ -326,6 +327,11 @@ export async function runEmbeddedAttempt(params) {
326
327
  tools,
327
328
  });
328
329
  const systemPrompt = createSystemPromptOverride(appendPrompt);
330
+ // Fire-and-forget: log the full system prompt text to disk
331
+ const sessionKeyForLog = params.sessionKey ?? params.sessionId;
332
+ if (sessionKeyForLog) {
333
+ writeSystemPromptLog(sessionKeyForLog, appendPrompt).catch(() => { });
334
+ }
329
335
  const sessionLock = await acquireSessionWriteLock({
330
336
  sessionFile: params.sessionFile,
331
337
  });
@@ -0,0 +1,24 @@
1
+ import fs from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { createSubsystemLogger } from "../logging/subsystem.js";
5
+ const log = createSubsystemLogger("system-prompt-log");
6
+ const DEFAULT_DIR = path.join(os.homedir(), ".taskmaster", "logs", "system-prompts");
7
+ const MAX_KEY_LENGTH = 128;
8
+ export function sanitiseSessionKey(key) {
9
+ const cleaned = key.replace(/[:/\\]/g, "_").trim();
10
+ if (!cleaned)
11
+ return "_unknown_";
12
+ return cleaned.slice(0, MAX_KEY_LENGTH);
13
+ }
14
+ export async function writeSystemPromptLog(sessionKey, systemPrompt, dir = DEFAULT_DIR) {
15
+ try {
16
+ const fileName = `${sanitiseSessionKey(sessionKey)}.txt`;
17
+ await fs.mkdir(dir, { recursive: true });
18
+ await fs.writeFile(path.join(dir, fileName), systemPrompt, "utf-8");
19
+ }
20
+ catch (err) {
21
+ const message = err instanceof Error ? err.message : String(err);
22
+ log.warn(`Failed to write system prompt log: ${message}`);
23
+ }
24
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.24.0",
3
- "commit": "83d5e346e0e5015508f4c0771f27c53e03f49fa8",
4
- "builtAt": "2026-03-07T09:26:50.521Z"
2
+ "version": "1.25.1",
3
+ "commit": "cab74729d527161291e19519e78fd0f9202db79e",
4
+ "builtAt": "2026-03-07T12:55:01.318Z"
5
5
  }
@@ -12,7 +12,7 @@ export const DEFAULT_LOG_DIR = "/tmp/taskmaster";
12
12
  export const DEFAULT_LOG_FILE = path.join(DEFAULT_LOG_DIR, "taskmaster.log"); // legacy single-file path
13
13
  const LOG_PREFIX = "taskmaster";
14
14
  const LOG_SUFFIX = ".log";
15
- const MAX_LOG_AGE_MS = 24 * 60 * 60 * 1000; // 24h
15
+ const MAX_LOG_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
16
16
  const requireConfig = createRequire(import.meta.url);
17
17
  const externalTransports = new Set();
18
18
  function attachExternalTransport(logger, transport) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/taskmaster",
3
- "version": "1.24.0",
3
+ "version": "1.25.1",
4
4
  "description": "AI-powered business assistant for small businesses",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -214,6 +214,39 @@ WATCHTMR
214
214
  as_root systemctl enable --now avahi-watchdog.timer 2>/dev/null || true
215
215
  echo " mDNS watchdog enabled"
216
216
 
217
+ # Wi-Fi power management off — prevents radio stalls under sustained traffic (e.g. Tailscale)
218
+ as_root tee /etc/udev/rules.d/70-wifi-powersave.rules > /dev/null << 'UDEVRULE'
219
+ ACTION=="add", SUBSYSTEM=="net", KERNEL=="wlan*", RUN+="/usr/sbin/iw dev $name set power_save off"
220
+ UDEVRULE
221
+ iw dev wlan0 set power_save off 2>/dev/null || true
222
+ echo " Wi-Fi power save disabled"
223
+
224
+ # Wi-Fi watchdog — reconnects wlan0 if the radio stalls (interface up but no connectivity)
225
+ as_root tee /etc/systemd/system/wifi-watchdog.service > /dev/null << 'WIFISVC'
226
+ [Unit]
227
+ Description=Reconnect Wi-Fi if connectivity is lost
228
+
229
+ [Service]
230
+ Type=oneshot
231
+ ExecCondition=/bin/sh -c '[ -d /sys/class/net/wlan0 ] && ! ping -c 1 -W 5 1.1.1.1 >/dev/null 2>&1'
232
+ ExecStart=/usr/bin/nmcli device disconnect wlan0
233
+ ExecStart=/usr/bin/nmcli device connect wlan0
234
+ WIFISVC
235
+ as_root tee /etc/systemd/system/wifi-watchdog.timer > /dev/null << 'WIFITMR'
236
+ [Unit]
237
+ Description=Check Wi-Fi connectivity every 2 minutes
238
+
239
+ [Timer]
240
+ OnBootSec=3min
241
+ OnUnitActiveSec=2min
242
+
243
+ [Install]
244
+ WantedBy=timers.target
245
+ WIFITMR
246
+ as_root systemctl daemon-reload
247
+ as_root systemctl enable --now wifi-watchdog.timer 2>/dev/null || true
248
+ echo " Wi-Fi watchdog enabled"
249
+
217
250
  # Tailscale (for optional internet access via Funnel)
218
251
  if ! command -v tailscale >/dev/null 2>&1; then
219
252
  curl -fsSL https://tailscale.com/install.sh | as_root sh >/dev/null 2>&1 \
@@ -1145,7 +1145,18 @@ If your control panel URL suddenly stops working and you notice the hostname has
1145
1145
 
1146
1146
  If you can reach your device by IP address (e.g. `http://192.168.88.11:18789`) but the `.local` address has stopped resolving, the mDNS service (Avahi) on the Pi has likely lost its network registration. This can happen after a Wi-Fi reconnection — the service is still running but has stopped responding to name lookups.
1147
1147
 
1148
- Taskmaster includes an mDNS watchdog that detects this and restarts the service automatically every 5 minutes. If your device is on v1.22 or later, this should self-heal within a few minutes. If it doesn't, reboot the device — the watchdog will be active after the reboot.
1148
+ Taskmaster includes an mDNS watchdog that detects this and restarts the service automatically every 5 minutes. If your device is on v1.23 or later, this should self-heal within a few minutes. If it doesn't, reboot the device — the watchdog will be active after the reboot.
1149
+
1150
+ #### Device completely unreachable (not even by IP)
1151
+
1152
+ If your Pi is powered on and connected to Wi-Fi but you can't reach it at all — not by `.local` name, not by IP address, and not even by ping — the Wi-Fi radio has likely stalled. This is a known Raspberry Pi hardware issue where the Wi-Fi chipset stops passing packets while still reporting as "connected". It can be triggered by sustained network traffic, including Tailscale's always-on tunnel.
1153
+
1154
+ Taskmaster v1.23+ includes two protections that prevent this:
1155
+
1156
+ - **Wi-Fi power save is disabled** — this prevents most radio stalls by keeping the chipset active
1157
+ - **A Wi-Fi watchdog** checks connectivity every 2 minutes and automatically reconnects Wi-Fi if traffic has stopped flowing
1158
+
1159
+ If your device is on v1.23 or later, connectivity should self-heal within 2 minutes. If you're on an older version, updating (`taskmaster update`) will install these protections automatically.
1149
1160
 
1150
1161
  ### Static IP Address
1151
1162