@rubytech/taskmaster 1.9.0 → 1.9.2
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
CHANGED
|
@@ -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)",
|
package/dist/cron/service/ops.js
CHANGED
|
@@ -23,6 +23,17 @@ export function stop(state) {
|
|
|
23
23
|
stopTimer(state);
|
|
24
24
|
}
|
|
25
25
|
export async function status(state) {
|
|
26
|
+
// When the store is already loaded, read without the lock. This avoids
|
|
27
|
+
// deadlock when an agent running inside a cron job calls cron.status —
|
|
28
|
+
// the lock is held by executeJob for the entire agent turn.
|
|
29
|
+
if (state.store) {
|
|
30
|
+
return {
|
|
31
|
+
enabled: state.deps.cronEnabled,
|
|
32
|
+
storePath: state.deps.storePath,
|
|
33
|
+
jobs: state.store.jobs.length,
|
|
34
|
+
nextWakeAtMs: state.deps.cronEnabled === true ? (nextWakeAtMs(state) ?? null) : null,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
26
37
|
return await locked(state, async () => {
|
|
27
38
|
await ensureLoaded(state);
|
|
28
39
|
return {
|
|
@@ -34,6 +45,14 @@ export async function status(state) {
|
|
|
34
45
|
});
|
|
35
46
|
}
|
|
36
47
|
export async function list(state, opts) {
|
|
48
|
+
// When the store is already loaded, read without the lock. This avoids
|
|
49
|
+
// deadlock when an agent running inside a cron job calls cron.list —
|
|
50
|
+
// the lock is held by executeJob for the entire agent turn.
|
|
51
|
+
if (state.store) {
|
|
52
|
+
const includeDisabled = opts?.includeDisabled === true;
|
|
53
|
+
const jobs = state.store.jobs.filter((j) => includeDisabled || j.enabled);
|
|
54
|
+
return jobs.sort((a, b) => (a.state.nextRunAtMs ?? 0) - (b.state.nextRunAtMs ?? 0));
|
|
55
|
+
}
|
|
37
56
|
return await locked(state, async () => {
|
|
38
57
|
await ensureLoaded(state);
|
|
39
58
|
const includeDisabled = opts?.includeDisabled === true;
|
|
@@ -99,19 +118,27 @@ export async function remove(state, id) {
|
|
|
99
118
|
});
|
|
100
119
|
}
|
|
101
120
|
export async function run(state, id, mode) {
|
|
102
|
-
|
|
121
|
+
// Resolve the job under the lock, then release before executing.
|
|
122
|
+
// executeJob runs a full agent turn whose tools may call back into the
|
|
123
|
+
// cron service. Holding the lock during execution causes a deadlock.
|
|
124
|
+
const resolved = await locked(state, async () => {
|
|
103
125
|
warnIfDisabled(state, "run");
|
|
104
126
|
await ensureLoaded(state);
|
|
105
127
|
const job = findJobOrThrow(state, id);
|
|
106
128
|
const now = state.deps.nowMs();
|
|
107
129
|
const due = isJobDue(job, now, { forced: mode === "force" });
|
|
108
130
|
if (!due)
|
|
109
|
-
return {
|
|
110
|
-
|
|
131
|
+
return { job: null, now: 0, notDue: true };
|
|
132
|
+
return { job, now, notDue: false };
|
|
133
|
+
});
|
|
134
|
+
if (resolved.notDue)
|
|
135
|
+
return { ok: true, ran: false, reason: "not-due" };
|
|
136
|
+
await executeJob(state, resolved.job, resolved.now, { forced: mode === "force" });
|
|
137
|
+
await locked(state, async () => {
|
|
111
138
|
await persist(state);
|
|
112
139
|
armTimer(state);
|
|
113
|
-
return { ok: true, ran: true };
|
|
114
140
|
});
|
|
141
|
+
return { ok: true, ran: true };
|
|
115
142
|
}
|
|
116
143
|
export function wakeNow(state, opts) {
|
|
117
144
|
return wake(state, opts);
|
|
@@ -26,9 +26,15 @@ export async function onTimer(state) {
|
|
|
26
26
|
return;
|
|
27
27
|
state.running = true;
|
|
28
28
|
try {
|
|
29
|
+
// Load store under the lock, then release before executing jobs.
|
|
30
|
+
// executeJob runs a full agent turn whose tools may call back into the
|
|
31
|
+
// cron service (e.g. cron.list). Holding the lock during execution
|
|
32
|
+
// causes a deadlock because the inner cron call waits for the same lock.
|
|
29
33
|
await locked(state, async () => {
|
|
30
34
|
await ensureLoaded(state);
|
|
31
|
-
|
|
35
|
+
});
|
|
36
|
+
await runDueJobs(state);
|
|
37
|
+
await locked(state, async () => {
|
|
32
38
|
await persist(state);
|
|
33
39
|
armTimer(state);
|
|
34
40
|
});
|
|
@@ -102,21 +102,16 @@ export async function startGatewayServer(port = 18789, opts = {}) {
|
|
|
102
102
|
.join("\n")}`);
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
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
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
@@ -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
|
|
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
|
|