@omnixal/openclaw-nats-plugin 0.2.0 → 0.2.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/cli/setup.ts +27 -0
- package/dashboard/bun.lock +253 -0
- package/dashboard/components.json +16 -0
- package/dashboard/index.html +22 -0
- package/dashboard/package.json +24 -0
- package/dashboard/src/App.svelte +107 -0
- package/dashboard/src/app.css +232 -0
- package/dashboard/src/lib/ConfigPanel.svelte +35 -0
- package/dashboard/src/lib/CronPanel.svelte +255 -0
- package/dashboard/src/lib/HealthCards.svelte +68 -0
- package/dashboard/src/lib/MetricsPanel.svelte +60 -0
- package/dashboard/src/lib/PendingTable.svelte +73 -0
- package/dashboard/src/lib/RoutesPanel.svelte +178 -0
- package/dashboard/src/lib/ThemeToggle.svelte +54 -0
- package/dashboard/src/lib/api.ts +141 -0
- package/dashboard/src/lib/components/ui/badge/badge.svelte +50 -0
- package/dashboard/src/lib/components/ui/badge/index.ts +2 -0
- package/dashboard/src/lib/components/ui/button/button.svelte +82 -0
- package/dashboard/src/lib/components/ui/button/index.ts +17 -0
- package/dashboard/src/lib/components/ui/card/card-action.svelte +20 -0
- package/dashboard/src/lib/components/ui/card/card-content.svelte +15 -0
- package/dashboard/src/lib/components/ui/card/card-description.svelte +20 -0
- package/dashboard/src/lib/components/ui/card/card-footer.svelte +20 -0
- package/dashboard/src/lib/components/ui/card/card-header.svelte +23 -0
- package/dashboard/src/lib/components/ui/card/card-title.svelte +20 -0
- package/dashboard/src/lib/components/ui/card/card.svelte +23 -0
- package/dashboard/src/lib/components/ui/card/index.ts +25 -0
- package/dashboard/src/lib/components/ui/table/index.ts +28 -0
- package/dashboard/src/lib/components/ui/table/table-body.svelte +20 -0
- package/dashboard/src/lib/components/ui/table/table-caption.svelte +20 -0
- package/dashboard/src/lib/components/ui/table/table-cell.svelte +23 -0
- package/dashboard/src/lib/components/ui/table/table-footer.svelte +20 -0
- package/dashboard/src/lib/components/ui/table/table-head.svelte +23 -0
- package/dashboard/src/lib/components/ui/table/table-header.svelte +20 -0
- package/dashboard/src/lib/components/ui/table/table-row.svelte +23 -0
- package/dashboard/src/lib/components/ui/table/table.svelte +22 -0
- package/dashboard/src/lib/utils.ts +29 -0
- package/dashboard/src/main.ts +7 -0
- package/dashboard/tsconfig.json +19 -0
- package/dashboard/vite.config.ts +30 -0
- package/package.json +6 -4
- package/plugins/nats-context-engine/http-handler.ts +1 -1
- package/plugins/nats-context-engine/index.ts +59 -0
- package/sidecar/bun.lock +8 -6
- package/sidecar/package.json +4 -4
- package/sidecar/src/app.module.ts +9 -2
- package/sidecar/src/consumer/consumer.controller.ts +4 -0
- package/sidecar/src/consumer/consumer.module.ts +2 -1
- package/sidecar/src/db/migrations/0004_familiar_zaladane.sql +17 -0
- package/sidecar/src/db/migrations/meta/0004_snapshot.json +306 -0
- package/sidecar/src/db/migrations/meta/_journal.json +7 -0
- package/sidecar/src/db/schema.ts +20 -0
- package/sidecar/src/health/health.service.ts +1 -1
- package/sidecar/src/index.ts +6 -0
- package/sidecar/src/metrics/metrics.controller.ts +16 -0
- package/sidecar/src/metrics/metrics.module.ts +10 -0
- package/sidecar/src/metrics/metrics.service.ts +64 -0
- package/sidecar/src/publisher/publisher.module.ts +2 -0
- package/sidecar/src/publisher/publisher.service.ts +6 -1
- package/sidecar/src/router/router.controller.ts +20 -12
- package/sidecar/src/router/router.repository.ts +39 -7
- package/sidecar/src/router/router.service.ts +10 -2
- package/sidecar/src/scheduler/scheduler.controller.ts +68 -0
- package/sidecar/src/scheduler/scheduler.module.ts +13 -0
- package/sidecar/src/scheduler/scheduler.repository.ts +64 -0
- package/sidecar/src/scheduler/scheduler.service.ts +138 -0
- package/sidecar/src/validation/schemas.ts +18 -0
- package/skills/nats-events/SKILL.md +41 -28
- package/dashboard/dist/assets/index--UFIkwvP.js +0 -2
- package/dashboard/dist/assets/index-CafgidIc.css +0 -2
- package/dashboard/dist/index.html +0 -13
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Controller, Get, Post, Patch, Delete,
|
|
3
|
+
Body, Param, BaseController,
|
|
4
|
+
UseMiddleware, Subscribe,
|
|
5
|
+
type Message,
|
|
6
|
+
type OneBunResponse,
|
|
7
|
+
} from '@onebun/core';
|
|
8
|
+
import { SchedulerService } from './scheduler.service';
|
|
9
|
+
import { ApiKeyMiddleware } from '../auth/api-key.middleware';
|
|
10
|
+
import { createCronBodySchema, type CreateCronBody } from '../validation/schemas';
|
|
11
|
+
|
|
12
|
+
@Controller('/api/cron')
|
|
13
|
+
@UseMiddleware(ApiKeyMiddleware)
|
|
14
|
+
export class SchedulerController extends BaseController {
|
|
15
|
+
constructor(private scheduler: SchedulerService) {
|
|
16
|
+
super();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@Post()
|
|
20
|
+
async createJob(@Body(createCronBodySchema) body: CreateCronBody): Promise<OneBunResponse> {
|
|
21
|
+
if (!body.subject.startsWith('agent.events.')) {
|
|
22
|
+
return this.error('subject must start with agent.events.', 400, 400);
|
|
23
|
+
}
|
|
24
|
+
const job = await this.scheduler.add({
|
|
25
|
+
name: body.name,
|
|
26
|
+
expr: body.cron,
|
|
27
|
+
subject: body.subject,
|
|
28
|
+
payload: body.payload,
|
|
29
|
+
timezone: body.timezone,
|
|
30
|
+
});
|
|
31
|
+
return this.success(job);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@Get()
|
|
35
|
+
async listJobs(): Promise<OneBunResponse> {
|
|
36
|
+
const jobs = await this.scheduler.list();
|
|
37
|
+
return this.success(jobs);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@Patch('/:name/toggle')
|
|
41
|
+
async toggleJob(@Param('name') name: string): Promise<OneBunResponse> {
|
|
42
|
+
const result = await this.scheduler.toggle(name);
|
|
43
|
+
if (!result) return this.error('Job not found', 404, 404);
|
|
44
|
+
return this.success(result);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@Post('/:name/run')
|
|
48
|
+
async runJob(@Param('name') name: string): Promise<OneBunResponse> {
|
|
49
|
+
const fired = await this.scheduler.fireNow(name);
|
|
50
|
+
if (!fired) return this.error('Job not found', 404, 404);
|
|
51
|
+
return this.success({ fired: true });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@Delete('/:name')
|
|
55
|
+
async deleteJob(@Param('name') name: string): Promise<OneBunResponse> {
|
|
56
|
+
const deleted = await this.scheduler.remove(name);
|
|
57
|
+
if (!deleted) return this.error('Job not found', 404, 404);
|
|
58
|
+
return this.success({ deleted: true });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@Subscribe('scheduler.fire.*')
|
|
62
|
+
async handleFire(message: Message<unknown>): Promise<void> {
|
|
63
|
+
const jobName = message.pattern?.split('.').pop();
|
|
64
|
+
if (jobName) {
|
|
65
|
+
await this.scheduler.handleFire(jobName);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Module } from '@onebun/core';
|
|
2
|
+
import { PublisherModule } from '../publisher/publisher.module';
|
|
3
|
+
import { SchedulerRepository } from './scheduler.repository';
|
|
4
|
+
import { SchedulerService } from './scheduler.service';
|
|
5
|
+
import { SchedulerController } from './scheduler.controller';
|
|
6
|
+
|
|
7
|
+
@Module({
|
|
8
|
+
imports: [PublisherModule],
|
|
9
|
+
controllers: [SchedulerController],
|
|
10
|
+
providers: [SchedulerRepository, SchedulerService],
|
|
11
|
+
exports: [SchedulerService],
|
|
12
|
+
})
|
|
13
|
+
export class SchedulerModule {}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Service, BaseService } from '@onebun/core';
|
|
2
|
+
import { DrizzleService, eq, sql } from '@onebun/drizzle';
|
|
3
|
+
import { cronJobs, type DbCronJob, type NewCronJob } from '../db/schema';
|
|
4
|
+
|
|
5
|
+
@Service()
|
|
6
|
+
export class SchedulerRepository extends BaseService {
|
|
7
|
+
constructor(private db: DrizzleService) {
|
|
8
|
+
super();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async upsert(job: NewCronJob): Promise<DbCronJob> {
|
|
12
|
+
const [result] = await this.db
|
|
13
|
+
.insert(cronJobs)
|
|
14
|
+
.values(job)
|
|
15
|
+
.onConflictDoUpdate({
|
|
16
|
+
target: cronJobs.name,
|
|
17
|
+
set: {
|
|
18
|
+
expr: sql`excluded.expr`,
|
|
19
|
+
subject: sql`excluded.subject`,
|
|
20
|
+
payload: sql`excluded.payload`,
|
|
21
|
+
timezone: sql`excluded.timezone`,
|
|
22
|
+
enabled: sql`excluded.enabled`,
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
.returning();
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async findAll(): Promise<DbCronJob[]> {
|
|
30
|
+
// drizzle type limitation: chained .orderBy() loses type info
|
|
31
|
+
return this.db.select().from(cronJobs).orderBy(cronJobs.name) as any;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async findAllEnabled(): Promise<DbCronJob[]> {
|
|
35
|
+
// drizzle type limitation: chained .where()/.orderBy() loses type info
|
|
36
|
+
return this.db.select().from(cronJobs)
|
|
37
|
+
.where(eq(cronJobs.enabled, true))
|
|
38
|
+
.orderBy(cronJobs.name) as any;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async findByName(name: string): Promise<DbCronJob | undefined> {
|
|
42
|
+
const [result] = await this.db.select().from(cronJobs)
|
|
43
|
+
.where(eq(cronJobs.name, name));
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async deleteByName(name: string): Promise<boolean> {
|
|
48
|
+
const result = await this.db.delete(cronJobs)
|
|
49
|
+
.where(eq(cronJobs.name, name)).returning();
|
|
50
|
+
return result.length > 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async setEnabled(name: string, enabled: boolean): Promise<void> {
|
|
54
|
+
await this.db.update(cronJobs)
|
|
55
|
+
.set({ enabled })
|
|
56
|
+
.where(eq(cronJobs.name, name));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async updateLastRun(name: string): Promise<void> {
|
|
60
|
+
await this.db.update(cronJobs)
|
|
61
|
+
.set({ lastRunAt: new Date() })
|
|
62
|
+
.where(eq(cronJobs.name, name));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { Service, BaseService, QueueService, OnModuleInit } from '@onebun/core';
|
|
2
|
+
import { SchedulerRepository } from './scheduler.repository';
|
|
3
|
+
import { PublisherService } from '../publisher/publisher.service';
|
|
4
|
+
import { ulid } from 'ulid';
|
|
5
|
+
|
|
6
|
+
interface AddJobInput {
|
|
7
|
+
name: string;
|
|
8
|
+
expr: string;
|
|
9
|
+
subject: string;
|
|
10
|
+
payload?: unknown;
|
|
11
|
+
timezone?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@Service()
|
|
15
|
+
export class SchedulerService extends BaseService implements OnModuleInit {
|
|
16
|
+
constructor(
|
|
17
|
+
private repo: SchedulerRepository,
|
|
18
|
+
private queueService: QueueService,
|
|
19
|
+
private publisher: PublisherService,
|
|
20
|
+
) {
|
|
21
|
+
super();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private get scheduler() {
|
|
25
|
+
return this.queueService.getScheduler();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async onModuleInit(): Promise<void> {
|
|
29
|
+
const jobs = await this.repo.findAllEnabled();
|
|
30
|
+
for (const job of jobs) {
|
|
31
|
+
this.scheduler.addCronJob(
|
|
32
|
+
job.name,
|
|
33
|
+
job.expr,
|
|
34
|
+
`scheduler.fire.${job.name}`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
if (jobs.length > 0) {
|
|
38
|
+
this.logger.info(`Restored ${jobs.length} cron jobs from DB`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async add(input: AddJobInput) {
|
|
43
|
+
const job = await this.repo.upsert({
|
|
44
|
+
id: ulid(),
|
|
45
|
+
name: input.name,
|
|
46
|
+
expr: input.expr,
|
|
47
|
+
subject: input.subject,
|
|
48
|
+
payload: input.payload ?? null,
|
|
49
|
+
timezone: input.timezone ?? 'UTC',
|
|
50
|
+
enabled: true,
|
|
51
|
+
createdAt: new Date(),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (this.scheduler.hasJob(input.name)) {
|
|
55
|
+
// Remove and re-add to update the cron expression
|
|
56
|
+
this.scheduler.removeJob(input.name);
|
|
57
|
+
}
|
|
58
|
+
this.scheduler.addCronJob(
|
|
59
|
+
input.name,
|
|
60
|
+
input.expr,
|
|
61
|
+
`scheduler.fire.${input.name}`,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
this.logger.info(`Cron job '${input.name}' registered: ${input.expr} -> ${input.subject}`);
|
|
65
|
+
return job;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async remove(name: string): Promise<boolean> {
|
|
69
|
+
const deleted = await this.repo.deleteByName(name);
|
|
70
|
+
if (this.scheduler.hasJob(name)) {
|
|
71
|
+
this.scheduler.removeJob(name);
|
|
72
|
+
}
|
|
73
|
+
return deleted;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async list() {
|
|
77
|
+
const dbJobs = await this.repo.findAll();
|
|
78
|
+
return dbJobs.map(job => {
|
|
79
|
+
const runtime = this.scheduler.getJob(job.name);
|
|
80
|
+
return {
|
|
81
|
+
...job,
|
|
82
|
+
nextRun: runtime?.nextRun ?? null,
|
|
83
|
+
isRunning: runtime?.isRunning ?? false,
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async toggle(name: string) {
|
|
89
|
+
const job = await this.repo.findByName(name);
|
|
90
|
+
if (!job) return null;
|
|
91
|
+
|
|
92
|
+
const newEnabled = !job.enabled;
|
|
93
|
+
await this.repo.setEnabled(name, newEnabled);
|
|
94
|
+
|
|
95
|
+
if (newEnabled) {
|
|
96
|
+
this.scheduler.addCronJob(
|
|
97
|
+
name,
|
|
98
|
+
job.expr,
|
|
99
|
+
`scheduler.fire.${name}`,
|
|
100
|
+
);
|
|
101
|
+
this.logger.info(`Cron job '${name}' enabled`);
|
|
102
|
+
} else {
|
|
103
|
+
if (this.scheduler.hasJob(name)) {
|
|
104
|
+
this.scheduler.removeJob(name);
|
|
105
|
+
}
|
|
106
|
+
this.logger.info(`Cron job '${name}' disabled`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return this.repo.findByName(name);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async fireNow(name: string): Promise<boolean> {
|
|
113
|
+
const job = await this.repo.findByName(name);
|
|
114
|
+
if (!job) return false;
|
|
115
|
+
|
|
116
|
+
const base = (job.payload && typeof job.payload === 'object' && !Array.isArray(job.payload))
|
|
117
|
+
? (job.payload as Record<string, unknown>)
|
|
118
|
+
: {};
|
|
119
|
+
const payload = { ...base, _cron: { jobName: job.name, firedAt: new Date().toISOString(), manual: true } };
|
|
120
|
+
await this.publisher.publish(job.subject, payload);
|
|
121
|
+
await this.repo.updateLastRun(name);
|
|
122
|
+
this.logger.info(`Cron job '${name}' manually fired -> ${job.subject}`);
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async handleFire(jobName: string): Promise<void> {
|
|
127
|
+
const job = await this.repo.findByName(jobName);
|
|
128
|
+
if (!job || !job.enabled) return;
|
|
129
|
+
|
|
130
|
+
const base = (job.payload && typeof job.payload === 'object' && !Array.isArray(job.payload))
|
|
131
|
+
? (job.payload as Record<string, unknown>)
|
|
132
|
+
: {};
|
|
133
|
+
const payload = { ...base, _cron: { jobName: job.name, firedAt: new Date().toISOString() } };
|
|
134
|
+
await this.publisher.publish(job.subject, payload);
|
|
135
|
+
await this.repo.updateLastRun(job.name);
|
|
136
|
+
this.logger.debug(`Cron fired: ${job.name} -> ${job.subject}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -17,3 +17,21 @@ export const markDeliveredBodySchema = type({
|
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
export type MarkDeliveredBody = typeof markDeliveredBodySchema.infer;
|
|
20
|
+
|
|
21
|
+
export const createRouteBodySchema = type({
|
|
22
|
+
pattern: 'string',
|
|
23
|
+
'target?': 'string',
|
|
24
|
+
'priority?': 'number',
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export type CreateRouteBody = typeof createRouteBodySchema.infer;
|
|
28
|
+
|
|
29
|
+
export const createCronBodySchema = type({
|
|
30
|
+
name: 'string',
|
|
31
|
+
cron: 'string',
|
|
32
|
+
subject: 'string',
|
|
33
|
+
'payload?': 'unknown',
|
|
34
|
+
'timezone?': 'string',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export type CreateCronBody = typeof createCronBodySchema.infer;
|
|
@@ -5,22 +5,21 @@ description: Event-driven patterns for NATS. Use when the agent needs to publish
|
|
|
5
5
|
|
|
6
6
|
# NATS Event Bus
|
|
7
7
|
|
|
8
|
-
You have access to a NATS JetStream event bus via
|
|
8
|
+
You have access to a NATS JetStream event bus via these tools:
|
|
9
9
|
|
|
10
10
|
| Tool | Purpose |
|
|
11
11
|
|---|---|
|
|
12
12
|
| `nats_publish` | Publish an event (subject + payload) |
|
|
13
|
-
| `nats_subscribe` | Create a routing rule (pattern -> session) |
|
|
13
|
+
| `nats_subscribe` | Create a routing rule (pattern -> session) — idempotent, safe to repeat |
|
|
14
14
|
| `nats_unsubscribe` | Remove a routing rule by ID |
|
|
15
15
|
| `nats_subscriptions` | List routing rules (optional filters: pattern, target) |
|
|
16
|
+
| `nats_cron_add` | Schedule a recurring NATS event (no LLM wake) |
|
|
17
|
+
| `nats_cron_remove` | Remove a scheduled cron job |
|
|
18
|
+
| `nats_cron_list` | List all scheduled cron jobs |
|
|
16
19
|
|
|
17
20
|
## Bootstrap
|
|
18
21
|
|
|
19
|
-
Before first use,
|
|
20
|
-
|
|
21
|
-
1. Call `nats_subscriptions` with no arguments
|
|
22
|
-
2. If the list is **non-empty** — setup is complete, proceed with your task
|
|
23
|
-
3. If **empty** — run initial setup:
|
|
22
|
+
Before first use, set up default subscriptions. This is idempotent — safe to run multiple times:
|
|
24
23
|
|
|
25
24
|
```
|
|
26
25
|
nats_subscribe(pattern: "agent.events.cron.>", target: "main")
|
|
@@ -29,30 +28,45 @@ nats_subscribe(pattern: "agent.events.hook.>", target: "main")
|
|
|
29
28
|
nats_subscribe(pattern: "agent.events.custom.>", target: "main")
|
|
30
29
|
```
|
|
31
30
|
|
|
32
|
-
Do NOT repeat bootstrap if subscriptions already exist.
|
|
33
|
-
|
|
34
31
|
## Event-Driven Rules
|
|
35
32
|
|
|
36
|
-
1. **Never poll** — subscribe to events and react when they arrive as messages
|
|
37
|
-
2. **
|
|
38
|
-
3. **Every scheduled task** = cron entry calling `nats-cron-trigger.sh` + subscription on that subject
|
|
39
|
-
4. **Incoming events** arrive as chat messages formatted `[NATS:<subject>] <payload>`
|
|
33
|
+
1. **Never poll** — subscribe to events and react when they arrive as messages
|
|
34
|
+
2. **Incoming events** arrive as chat messages formatted `[NATS:<subject>] <payload>`
|
|
40
35
|
|
|
41
|
-
##
|
|
36
|
+
## Scheduling Events (Cron)
|
|
42
37
|
|
|
43
|
-
|
|
38
|
+
Use `nats_cron_add` for scheduled events. This fires directly without waking the LLM:
|
|
44
39
|
|
|
45
|
-
```
|
|
46
|
-
#
|
|
47
|
-
|
|
40
|
+
```
|
|
41
|
+
# Schedule a daily report trigger at 9am UTC
|
|
42
|
+
nats_cron_add(
|
|
43
|
+
name: "daily-report",
|
|
44
|
+
cron: "0 9 * * *",
|
|
45
|
+
subject: "agent.events.cron.daily-report",
|
|
46
|
+
payload: { "task": "daily_report" }
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Schedule hourly revenue check
|
|
50
|
+
nats_cron_add(
|
|
51
|
+
name: "hourly-check",
|
|
52
|
+
cron: "0 * * * *",
|
|
53
|
+
subject: "agent.events.cron.check-revenue",
|
|
54
|
+
payload: { "task": "check_revenue" }
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# List all scheduled jobs
|
|
58
|
+
nats_cron_list()
|
|
59
|
+
|
|
60
|
+
# Remove a job
|
|
61
|
+
nats_cron_remove(name: "hourly-check")
|
|
62
|
+
```
|
|
48
63
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
0 9 * * * /path/to/nats-cron-trigger.sh agent.events.cron.daily-report '{"task":"daily_report"}'
|
|
64
|
+
Don't forget to also subscribe to the cron subject so you receive the events:
|
|
65
|
+
```
|
|
66
|
+
nats_subscribe(pattern: "agent.events.cron.>", target: "main")
|
|
53
67
|
```
|
|
54
68
|
|
|
55
|
-
|
|
69
|
+
**Alternative (environments with system crontab):** Use `nats-cron-trigger.sh` script.
|
|
56
70
|
|
|
57
71
|
## Subject Hierarchy
|
|
58
72
|
|
|
@@ -78,16 +92,15 @@ The script only needs `NATS_SIDECAR_URL` and `NATS_PLUGIN_API_KEY` environment v
|
|
|
78
92
|
**React to subagent completion:**
|
|
79
93
|
```
|
|
80
94
|
nats_subscribe(pattern: "agent.events.subagent.ended", target: "main")
|
|
81
|
-
# When subagent finishes, you receive: [NATS:agent.events.subagent.ended] {"subagentId":...,"result":...}
|
|
82
95
|
```
|
|
83
96
|
|
|
84
|
-
**Publish a custom event
|
|
97
|
+
**Publish a custom event:**
|
|
85
98
|
```
|
|
86
99
|
nats_publish(subject: "agent.events.custom.report-ready", payload: {"reportUrl": "https://..."})
|
|
87
100
|
```
|
|
88
101
|
|
|
89
|
-
**
|
|
102
|
+
**Set up daily workflow:**
|
|
90
103
|
```
|
|
91
|
-
nats_subscribe(pattern: "agent.events.cron.
|
|
92
|
-
|
|
104
|
+
nats_subscribe(pattern: "agent.events.cron.daily-report", target: "main")
|
|
105
|
+
nats_cron_add(name: "daily-report", cron: "0 9 * * *", subject: "agent.events.cron.daily-report", payload: {"task": "report"})
|
|
93
106
|
```
|