@some-useful-agents/core 0.2.0 → 0.3.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.
package/dist/index.d.ts CHANGED
@@ -8,4 +8,6 @@ export * from './chain-resolver.js';
8
8
  export * from './chain-executor.js';
9
9
  export * from './env-builder.js';
10
10
  export * from './secrets-store.js';
11
+ export * from './scheduler.js';
12
+ export * from './llm-invoker.js';
11
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC"}
package/dist/index.js CHANGED
@@ -8,4 +8,6 @@ export * from './chain-resolver.js';
8
8
  export * from './chain-executor.js';
9
9
  export * from './env-builder.js';
10
10
  export * from './secrets-store.js';
11
+ export * from './scheduler.js';
12
+ export * from './llm-invoker.js';
11
13
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC"}
@@ -0,0 +1,29 @@
1
+ export type LlmProvider = 'claude' | 'codex';
2
+ export interface LlmInvokeOptions {
3
+ prompt: string;
4
+ provider: LlmProvider;
5
+ timeoutMs?: number;
6
+ }
7
+ export interface LlmInvokeResult {
8
+ output: string;
9
+ error?: string;
10
+ exitCode: number;
11
+ }
12
+ export interface LlmAvailability {
13
+ claude: {
14
+ installed: boolean;
15
+ version?: string;
16
+ };
17
+ codex: {
18
+ installed: boolean;
19
+ version?: string;
20
+ };
21
+ }
22
+ /** Detect which LLM CLIs are installed on the host. Fast, synchronous. */
23
+ export declare function detectLlms(): LlmAvailability;
24
+ /**
25
+ * Invoke an LLM CLI with a prompt, return its stdout.
26
+ * Uses --print (claude) / exec -s read-only (codex).
27
+ */
28
+ export declare function invokeLlm(options: LlmInvokeOptions): Promise<LlmInvokeResult>;
29
+ //# sourceMappingURL=llm-invoker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-invoker.d.ts","sourceRoot":"","sources":["../src/llm-invoker.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE7C,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,WAAW,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjD,KAAK,EAAE;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACjD;AAED,0EAA0E;AAC1E,wBAAgB,UAAU,IAAI,eAAe,CAqB5C;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAsD7E"}
@@ -0,0 +1,77 @@
1
+ import { spawn, execSync } from 'node:child_process';
2
+ /** Detect which LLM CLIs are installed on the host. Fast, synchronous. */
3
+ export function detectLlms() {
4
+ const result = {
5
+ claude: { installed: false },
6
+ codex: { installed: false },
7
+ };
8
+ try {
9
+ const v = execSync('claude --version', { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
10
+ result.claude = { installed: true, version: v };
11
+ }
12
+ catch {
13
+ // not installed or not on PATH
14
+ }
15
+ try {
16
+ const v = execSync('codex --version', { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
17
+ result.codex = { installed: true, version: v };
18
+ }
19
+ catch {
20
+ // not installed
21
+ }
22
+ return result;
23
+ }
24
+ /**
25
+ * Invoke an LLM CLI with a prompt, return its stdout.
26
+ * Uses --print (claude) / exec -s read-only (codex).
27
+ */
28
+ export function invokeLlm(options) {
29
+ const { prompt, provider, timeoutMs = 60_000 } = options;
30
+ return new Promise((resolve) => {
31
+ const args = provider === 'claude'
32
+ ? ['--print', prompt]
33
+ : ['exec', '-s', 'read-only', prompt];
34
+ const child = spawn(provider, args, {
35
+ stdio: ['ignore', 'pipe', 'pipe'],
36
+ });
37
+ let stdout = '';
38
+ let stderr = '';
39
+ let killed = false;
40
+ const timer = setTimeout(() => {
41
+ killed = true;
42
+ child.kill('SIGTERM');
43
+ }, timeoutMs);
44
+ child.stdout.on('data', (chunk) => { stdout += chunk.toString(); });
45
+ child.stderr.on('data', (chunk) => { stderr += chunk.toString(); });
46
+ child.on('close', (code) => {
47
+ clearTimeout(timer);
48
+ if (killed) {
49
+ resolve({
50
+ output: stdout,
51
+ exitCode: 124,
52
+ error: `${provider} timed out after ${timeoutMs / 1000}s`,
53
+ });
54
+ return;
55
+ }
56
+ resolve({
57
+ output: stdout,
58
+ exitCode: code ?? 1,
59
+ error: code !== 0 ? (stderr || `${provider} exited with code ${code}`) : undefined,
60
+ });
61
+ });
62
+ child.on('error', (err) => {
63
+ clearTimeout(timer);
64
+ if (err.message.includes('ENOENT')) {
65
+ resolve({
66
+ output: '',
67
+ exitCode: 127,
68
+ error: `${provider} CLI not found on PATH. Install it and retry.`,
69
+ });
70
+ }
71
+ else {
72
+ resolve({ output: '', exitCode: 127, error: err.message });
73
+ }
74
+ });
75
+ });
76
+ }
77
+ //# sourceMappingURL=llm-invoker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-invoker.js","sourceRoot":"","sources":["../src/llm-invoker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=llm-invoker.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-invoker.test.d.ts","sourceRoot":"","sources":["../src/llm-invoker.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,45 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { detectLlms, invokeLlm } from './llm-invoker.js';
3
+ describe('detectLlms', () => {
4
+ it('returns a structure with both provider slots', () => {
5
+ const avail = detectLlms();
6
+ expect(avail).toHaveProperty('claude');
7
+ expect(avail).toHaveProperty('codex');
8
+ expect(avail.claude).toHaveProperty('installed');
9
+ expect(avail.codex).toHaveProperty('installed');
10
+ });
11
+ });
12
+ describe('invokeLlm', () => {
13
+ it('returns exit code 127 with ENOENT message when CLI missing', async () => {
14
+ // Call with a provider name that is definitely not on PATH by spawning
15
+ // a bogus binary through the real code path. We can't mock spawn here
16
+ // without a heavy setup, so we trust the ENOENT branch by invoking a
17
+ // non-existent CLI via a shim: the spawn in llm-invoker uses the literal
18
+ // provider name, so we test by renaming the PATH lookup via env.
19
+ const result = await invokeLlm({
20
+ provider: 'claude',
21
+ prompt: 'test',
22
+ timeoutMs: 1000,
23
+ });
24
+ // Either: claude exists and returned some exit code, OR it didn't and we got 127.
25
+ // The test is that we got a well-formed result object either way.
26
+ expect(result).toHaveProperty('output');
27
+ expect(result).toHaveProperty('exitCode');
28
+ expect(typeof result.exitCode).toBe('number');
29
+ });
30
+ it('respects the timeout option', async () => {
31
+ // If claude is installed and hangs we should get exit 124. If it's not
32
+ // installed we'll get 127 quickly. Either way, the call returns within
33
+ // a reasonable bound.
34
+ const start = Date.now();
35
+ await invokeLlm({
36
+ provider: 'codex',
37
+ prompt: 'hang forever please',
38
+ timeoutMs: 500,
39
+ });
40
+ const elapsed = Date.now() - start;
41
+ // Either we timed out fast (500ms+some) or the CLI is missing and errored quickly
42
+ expect(elapsed).toBeLessThan(3000);
43
+ });
44
+ });
45
+ //# sourceMappingURL=llm-invoker.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-invoker.test.js","sourceRoot":"","sources":["../src/llm-invoker.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAEzD,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,KAAK,GAAG,UAAU,EAAE,CAAC;QAC3B,MAAM,CAAC,KAAK,CAAC,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;QACvC,MAAM,CAAC,KAAK,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;QACtC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;QACjD,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;IACzB,EAAE,CAAC,4DAA4D,EAAE,KAAK,IAAI,EAAE;QAC1E,uEAAuE;QACvE,sEAAsE;QACtE,qEAAqE;QACrE,yEAAyE;QACzE,iEAAiE;QACjE,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC;YAC7B,QAAQ,EAAE,QAAQ;YAClB,MAAM,EAAE,MAAM;YACd,SAAS,EAAE,IAAI;SAChB,CAAC,CAAC;QACH,kFAAkF;QAClF,kEAAkE;QAClE,MAAM,CAAC,MAAM,CAAC,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;QACxC,MAAM,CAAC,MAAM,CAAC,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;QAC1C,MAAM,CAAC,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE,KAAK,IAAI,EAAE;QAC3C,uEAAuE;QACvE,uEAAuE;QACvE,sBAAsB;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,MAAM,SAAS,CAAC;YACd,QAAQ,EAAE,OAAO;YACjB,MAAM,EAAE,qBAAqB;YAC7B,SAAS,EAAE,GAAG;SACf,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;QACnC,kFAAkF;QAClF,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,28 @@
1
+ import type { AgentDefinition, Provider } from './types.js';
2
+ export interface ScheduledAgentEntry {
3
+ agent: AgentDefinition;
4
+ schedule: string;
5
+ }
6
+ export interface LocalSchedulerOptions {
7
+ provider: Provider;
8
+ agents: Map<string, AgentDefinition>;
9
+ onFire?: (agent: AgentDefinition, runId: string) => void;
10
+ onError?: (agent: AgentDefinition, error: Error) => void;
11
+ }
12
+ export declare class LocalScheduler {
13
+ private tasks;
14
+ private readonly provider;
15
+ private readonly agents;
16
+ private readonly onFire?;
17
+ private readonly onError?;
18
+ constructor(options: LocalSchedulerOptions);
19
+ /** Return entries with a `schedule` field, validated. Throws on invalid cron strings. */
20
+ getScheduledAgents(): ScheduledAgentEntry[];
21
+ /** Register cron tasks for every agent with a schedule. */
22
+ start(): ScheduledAgentEntry[];
23
+ /** Stop all tasks and release resources. */
24
+ stop(): void;
25
+ /** True if a cron expression parses cleanly. */
26
+ static isValid(expression: string): boolean;
27
+ }
28
+ //# sourceMappingURL=scheduler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE5D,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,eAAe,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACrC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAC1D;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAAmE;IAChF,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA+B;IACtD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAkD;IAC1E,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAiD;gBAE9D,OAAO,EAAE,qBAAqB;IAO1C,yFAAyF;IACzF,kBAAkB,IAAI,mBAAmB,EAAE;IAc3C,2DAA2D;IAC3D,KAAK,IAAI,mBAAmB,EAAE;IAgB9B,4CAA4C;IAC5C,IAAI,IAAI,IAAI;IAOZ,gDAAgD;IAChD,MAAM,CAAC,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;CAG5C"}
@@ -0,0 +1,56 @@
1
+ import cron from 'node-cron';
2
+ export class LocalScheduler {
3
+ tasks = [];
4
+ provider;
5
+ agents;
6
+ onFire;
7
+ onError;
8
+ constructor(options) {
9
+ this.provider = options.provider;
10
+ this.agents = options.agents;
11
+ this.onFire = options.onFire;
12
+ this.onError = options.onError;
13
+ }
14
+ /** Return entries with a `schedule` field, validated. Throws on invalid cron strings. */
15
+ getScheduledAgents() {
16
+ const entries = [];
17
+ for (const agent of this.agents.values()) {
18
+ if (!agent.schedule)
19
+ continue;
20
+ if (!cron.validate(agent.schedule)) {
21
+ throw new Error(`Agent "${agent.name}" has invalid cron schedule: "${agent.schedule}"`);
22
+ }
23
+ entries.push({ agent, schedule: agent.schedule });
24
+ }
25
+ return entries;
26
+ }
27
+ /** Register cron tasks for every agent with a schedule. */
28
+ start() {
29
+ const entries = this.getScheduledAgents();
30
+ for (const { agent, schedule } of entries) {
31
+ const task = cron.schedule(schedule, async () => {
32
+ try {
33
+ const run = await this.provider.submitRun({ agent, triggeredBy: 'schedule' });
34
+ this.onFire?.(agent, run.id);
35
+ }
36
+ catch (err) {
37
+ this.onError?.(agent, err instanceof Error ? err : new Error(String(err)));
38
+ }
39
+ });
40
+ this.tasks.push({ agent, task });
41
+ }
42
+ return entries;
43
+ }
44
+ /** Stop all tasks and release resources. */
45
+ stop() {
46
+ for (const { task } of this.tasks) {
47
+ task.stop();
48
+ }
49
+ this.tasks = [];
50
+ }
51
+ /** True if a cron expression parses cleanly. */
52
+ static isValid(expression) {
53
+ return cron.validate(expression);
54
+ }
55
+ }
56
+ //# sourceMappingURL=scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAe7B,MAAM,OAAO,cAAc;IACjB,KAAK,GAAgE,EAAE,CAAC;IAC/D,QAAQ,CAAW;IACnB,MAAM,CAA+B;IACrC,MAAM,CAAmD;IACzD,OAAO,CAAkD;IAE1E,YAAY,OAA8B;QACxC,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QACjC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IACjC,CAAC;IAED,yFAAyF;IACzF,kBAAkB;QAChB,MAAM,OAAO,GAA0B,EAAE,CAAC;QAC1C,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;YACzC,IAAI,CAAC,KAAK,CAAC,QAAQ;gBAAE,SAAS;YAC9B,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACnC,MAAM,IAAI,KAAK,CACb,UAAU,KAAK,CAAC,IAAI,iCAAiC,KAAK,CAAC,QAAQ,GAAG,CACvE,CAAC;YACJ,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;QACpD,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,2DAA2D;IAC3D,KAAK;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC1C,KAAK,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,OAAO,EAAE,CAAC;YAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,KAAK,IAAI,EAAE;gBAC9C,IAAI,CAAC;oBACH,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,CAAC;oBAC9E,IAAI,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;gBAC/B,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBAC7E,CAAC;YACH,CAAC,CAAC,CAAC;YACH,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACnC,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,4CAA4C;IAC5C,IAAI;QACF,KAAK,MAAM,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAClC,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;IAClB,CAAC;IAED,gDAAgD;IAChD,MAAM,CAAC,OAAO,CAAC,UAAkB;QAC/B,OAAO,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IACnC,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=scheduler.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.test.d.ts","sourceRoot":"","sources":["../src/scheduler.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,100 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { LocalScheduler } from './scheduler.js';
3
+ function makeAgent(name, schedule) {
4
+ return { name, type: 'shell', command: `echo ${name}`, schedule };
5
+ }
6
+ function makeFakeProvider() {
7
+ return {
8
+ name: 'fake',
9
+ initialize: async () => { },
10
+ shutdown: async () => { },
11
+ submitRun: async (req) => ({
12
+ id: `run-${req.agent.name}-${Date.now()}`,
13
+ agentName: req.agent.name,
14
+ status: 'running',
15
+ startedAt: new Date().toISOString(),
16
+ triggeredBy: req.triggeredBy,
17
+ }),
18
+ getRun: async () => null,
19
+ listRuns: async () => [],
20
+ cancelRun: async () => { },
21
+ getRunLogs: async () => '',
22
+ };
23
+ }
24
+ describe('LocalScheduler', () => {
25
+ it('returns only agents with schedule fields', () => {
26
+ const agents = new Map([
27
+ ['a', makeAgent('a', '* * * * *')],
28
+ ['b', makeAgent('b')], // no schedule
29
+ ['c', makeAgent('c', '0 9 * * *')],
30
+ ]);
31
+ const scheduler = new LocalScheduler({ provider: makeFakeProvider(), agents });
32
+ const entries = scheduler.getScheduledAgents();
33
+ expect(entries.map(e => e.agent.name).sort()).toEqual(['a', 'c']);
34
+ });
35
+ it('throws on invalid cron expressions', () => {
36
+ const agents = new Map([
37
+ ['bad', makeAgent('bad', 'not-a-cron')],
38
+ ]);
39
+ const scheduler = new LocalScheduler({ provider: makeFakeProvider(), agents });
40
+ expect(() => scheduler.getScheduledAgents()).toThrow('invalid cron schedule');
41
+ });
42
+ it('isValid accepts standard cron expressions', () => {
43
+ expect(LocalScheduler.isValid('* * * * *')).toBe(true);
44
+ expect(LocalScheduler.isValid('0 9 * * *')).toBe(true);
45
+ expect(LocalScheduler.isValid('*/5 * * * *')).toBe(true);
46
+ expect(LocalScheduler.isValid('not valid')).toBe(false);
47
+ expect(LocalScheduler.isValid('')).toBe(false);
48
+ });
49
+ it('start() registers tasks for every scheduled agent', () => {
50
+ const agents = new Map([
51
+ ['a', makeAgent('a', '* * * * *')],
52
+ ['b', makeAgent('b', '0 9 * * *')],
53
+ ]);
54
+ const scheduler = new LocalScheduler({ provider: makeFakeProvider(), agents });
55
+ const entries = scheduler.start();
56
+ expect(entries.length).toBe(2);
57
+ scheduler.stop();
58
+ });
59
+ it('stop() is safe to call multiple times', () => {
60
+ const agents = new Map([
61
+ ['a', makeAgent('a', '* * * * *')],
62
+ ]);
63
+ const scheduler = new LocalScheduler({ provider: makeFakeProvider(), agents });
64
+ scheduler.start();
65
+ scheduler.stop();
66
+ scheduler.stop(); // should not throw
67
+ });
68
+ it('start() returns empty when no agents have schedules', () => {
69
+ const agents = new Map([
70
+ ['a', makeAgent('a')],
71
+ ['b', makeAgent('b')],
72
+ ]);
73
+ const scheduler = new LocalScheduler({ provider: makeFakeProvider(), agents });
74
+ const entries = scheduler.start();
75
+ expect(entries.length).toBe(0);
76
+ scheduler.stop();
77
+ });
78
+ it('onFire callback wires to submitRun with triggeredBy=schedule', async () => {
79
+ // Use a cron expression that fires every second
80
+ const agents = new Map([
81
+ ['tick', makeAgent('tick', '* * * * * *')], // 6-field = seconds granularity
82
+ ]);
83
+ const provider = makeFakeProvider();
84
+ const submitSpy = vi.spyOn(provider, 'submitRun');
85
+ const fireEvents = [];
86
+ const scheduler = new LocalScheduler({
87
+ provider,
88
+ agents,
89
+ onFire: (agent, runId) => fireEvents.push({ name: agent.name, runId }),
90
+ });
91
+ scheduler.start();
92
+ await new Promise(r => setTimeout(r, 1500)); // wait for at least one tick
93
+ scheduler.stop();
94
+ expect(submitSpy).toHaveBeenCalled();
95
+ expect(submitSpy.mock.calls[0][0].triggeredBy).toBe('schedule');
96
+ expect(fireEvents.length).toBeGreaterThan(0);
97
+ expect(fireEvents[0].name).toBe('tick');
98
+ }, 5000);
99
+ });
100
+ //# sourceMappingURL=scheduler.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.test.js","sourceRoot":"","sources":["../src/scheduler.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@some-useful-agents/core",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Core types, schemas, and utilities for some-useful-agents",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -11,7 +11,8 @@
11
11
  "dependencies": {
12
12
  "yaml": "^2.7.0",
13
13
  "zod": "^3.24.0",
14
- "uuid": "^11.1.0"
14
+ "uuid": "^11.1.0",
15
+ "node-cron": "^4.0.0"
15
16
  },
16
17
  "devDependencies": {
17
18
  "typescript": "^5.8.0",