feat(cron): add agentId filtering to cron list

This commit is contained in:
clawsweeper
2026-05-05 00:48:57 +00:00
parent 1acc788f91
commit 9c5647c65d
4 changed files with 42 additions and 3 deletions

View File

@@ -23,7 +23,7 @@ Docs: https://docs.openclaw.ai
- Plugins/active-memory: skip session-store channel entries that contain `:` when resolving the recall subagent's channel, so QQ c2c agent IDs (e.g. `c2c:10D4F7C2…`) and other scoped conversation IDs do not reach bundled-plugin `dirName` validation and crash the recall run. The same guard already applied to explicit `channelId` params (#76704); this extends it to store-derived channels. (#77396) Thanks @hclsys.
- Secrets/external channel contracts: also look in `<rootDir>/dist/` when resolving the `secret-contract-api` sidecar, so npm-published externalized channel plugins (e.g. `@openclaw/discord` since 2026.5.2) whose compiled artifacts live under `dist/` actually contribute their channel SecretRef contracts to the runtime snapshot. Without this, env-backed `channels.discord.token` SecretRefs silently failed to resolve at gateway start on 2026.5.3, leaving the channel `not configured` even though #76449 had landed the generic external-contract loader. Thanks @mogglemoss.
- Models/auth: add `openclaw models auth list [--provider <id>] [--json]` so users can inspect saved per-agent auth profiles without dumping secrets or hitting the old “too many arguments” path. Thanks @vincentkoc.
- Cron CLI: add `openclaw cron list --agent <id>` and normalize the requested agent id before filtering, while keeping `cron list` unfiltered when no agent is supplied. Fixes #77118. Thanks @zhanggttry.
- Cron CLI: add `openclaw cron list --agent <id>`, normalize the requested agent id, and include jobs without a stored agent id under the configured default agent while keeping `cron list` unfiltered when no agent is supplied. Fixes #77118. Thanks @zhanggttry.
- Control UI/header: show the active agent name in dashboard breadcrumbs without adding the current session key, keeping non-chat views oriented without crowding the topbar.
- Control UI/cron: make the New Job sidebar collapsible so the jobs list can reclaim space while keeping the form one click away. Thanks @BunsDev.
- Gateway/startup: keep model-catalog test helpers, run-session lookup code, QR pairing helpers, and TypeBox memory-tool schema construction out of hot startup import paths, reducing default gateway benchmark plugin-load and memory pressure.

View File

@@ -218,7 +218,7 @@ openclaw cron run <job-id> --due
openclaw cron runs --id <job-id> --limit 50
```
`openclaw cron list` shows all matching jobs by default. Pass `--agent <id>` to show only jobs pinned to that normalized agent id.
`openclaw cron list` shows all matching jobs by default. Pass `--agent <id>` to show only jobs whose effective normalized agent id matches; jobs without a stored agent id count as the configured default agent.
`cron runs` entries include delivery diagnostics with the intended cron target, the resolved target, message-tool sends, fallback use, and delivered state.

View File

@@ -64,6 +64,33 @@ describe("cron listPage sort guards", () => {
expect(page.jobs.map((job) => job.id)).toEqual(["job-ops"]);
});
it("matches omitted job agent ids to the configured default agent when filtering", async () => {
const jobs = [
createBaseJob({ id: "job-main", agentId: "main", name: "main" }),
createBaseJob({ id: "job-ops", agentId: "ops", name: "ops" }),
createBaseJob({ id: "job-unset", agentId: undefined, name: "unset" }),
];
const state = createMockCronStateForJobs({ jobs });
state.deps.defaultAgentId = " Ops ";
const page = await listPage(state, { agentId: "ops" });
expect(page.jobs.map((job) => job.id)).toEqual(["job-ops", "job-unset"]);
});
it("matches omitted job agent ids to main when no default agent is configured", async () => {
const jobs = [
createBaseJob({ id: "job-main", agentId: "main", name: "main" }),
createBaseJob({ id: "job-ops", agentId: "ops", name: "ops" }),
createBaseJob({ id: "job-unset", agentId: undefined, name: "unset" }),
];
const state = createMockCronStateForJobs({ jobs });
const page = await listPage(state, { agentId: "main" });
expect(page.jobs.map((job) => job.id)).toEqual(["job-main", "job-unset"]);
});
it("keeps listPage unfiltered when agent id is omitted", async () => {
const jobs = [
createBaseJob({ id: "job-main", agentId: "main", name: "main" }),

View File

@@ -1,5 +1,6 @@
import { enqueueCommandInLane } from "../../process/command-queue.js";
import { CommandLane } from "../../process/lanes.js";
import { DEFAULT_AGENT_ID } from "../../routing/session-key.js";
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
import {
completeTaskRunByRunId,
@@ -273,6 +274,14 @@ function sortJobs(jobs: CronJob[], sortBy: CronJobsSortBy, sortDir: CronSortDir)
});
}
function resolveEffectiveJobAgentId(job: CronJob, defaultAgentId: string | undefined) {
return (
normalizeOptionalAgentId(job.agentId) ??
normalizeOptionalAgentId(defaultAgentId) ??
DEFAULT_AGENT_ID
);
}
export async function listPage(state: CronServiceState, opts?: CronListPageOptions) {
return await locked(state, async () => {
await ensureLoadedForRead(state);
@@ -289,7 +298,10 @@ export async function listPage(state: CronServiceState, opts?: CronListPageOptio
if (enabledFilter === "disabled" && isJobEnabled(job)) {
return false;
}
if (requestedAgentId && job.agentId !== requestedAgentId) {
if (
requestedAgentId &&
resolveEffectiveJobAgentId(job, state.deps.defaultAgentId) !== requestedAgentId
) {
return false;
}
if (!query) {