@kmmao/happy-agent 0.4.0 → 0.5.0
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.cjs +738 -123
- package/dist/index.d.cts +272 -1
- package/dist/index.d.mts +272 -1
- package/dist/index.mjs +739 -124
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -345,6 +345,272 @@ declare class TunnelManager {
|
|
|
345
345
|
stopRefresh(): void;
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
+
/**
|
|
349
|
+
* AutomationScheduler — lightweight in-memory job queue for agent triggers.
|
|
350
|
+
*
|
|
351
|
+
* Features:
|
|
352
|
+
* - Priority queue (urgent > user > background)
|
|
353
|
+
* - dedupeKey deduplication (queued/dispatching/running)
|
|
354
|
+
* - Concurrency limit (maxConcurrentJobs)
|
|
355
|
+
* - Retry with incremental backoff (attempt * retryDelayMs)
|
|
356
|
+
* - Ring buffer for recent completions (observability)
|
|
357
|
+
*/
|
|
358
|
+
type JobPriority = "urgent" | "user" | "background";
|
|
359
|
+
type JobStatus = "queued" | "dispatching" | "running" | "completed" | "failed";
|
|
360
|
+
interface SchedulerJob {
|
|
361
|
+
readonly id: string;
|
|
362
|
+
readonly kind: "webhook" | "supervisor" | "task";
|
|
363
|
+
readonly dedupeKey: string;
|
|
364
|
+
readonly priority: JobPriority;
|
|
365
|
+
status: JobStatus;
|
|
366
|
+
attempt: number;
|
|
367
|
+
readonly maxAttempts: number;
|
|
368
|
+
readonly createdAt: number;
|
|
369
|
+
updatedAt: number;
|
|
370
|
+
nextRunAt: number;
|
|
371
|
+
errorMessage?: string;
|
|
372
|
+
pid?: number;
|
|
373
|
+
/** The thunk that performs the actual work (spawn session etc.) */
|
|
374
|
+
readonly run: (jobId: string) => Promise<{
|
|
375
|
+
pid: number;
|
|
376
|
+
}>;
|
|
377
|
+
}
|
|
378
|
+
interface EnqueueOptions {
|
|
379
|
+
kind: SchedulerJob["kind"];
|
|
380
|
+
dedupeKey: string;
|
|
381
|
+
priority: JobPriority;
|
|
382
|
+
run: (jobId: string) => Promise<{
|
|
383
|
+
pid: number;
|
|
384
|
+
}>;
|
|
385
|
+
}
|
|
386
|
+
interface EnqueueResult {
|
|
387
|
+
job: SchedulerJob;
|
|
388
|
+
deduped: boolean;
|
|
389
|
+
}
|
|
390
|
+
interface SchedulerStatus {
|
|
391
|
+
queueLength: number;
|
|
392
|
+
runningCount: number;
|
|
393
|
+
recentCompletions: Array<{
|
|
394
|
+
id: string;
|
|
395
|
+
kind: string;
|
|
396
|
+
dedupeKey: string;
|
|
397
|
+
status: "completed" | "failed";
|
|
398
|
+
completedAt: number;
|
|
399
|
+
errorMessage?: string;
|
|
400
|
+
}>;
|
|
401
|
+
}
|
|
402
|
+
type AuditCallback = (event: {
|
|
403
|
+
kind: "job_enqueued" | "job_dispatched" | "job_completed" | "job_failed" | "job_retried";
|
|
404
|
+
jobId: string;
|
|
405
|
+
dedupeKey: string;
|
|
406
|
+
message?: string;
|
|
407
|
+
errorMessage?: string;
|
|
408
|
+
}) => void;
|
|
409
|
+
interface SchedulerOptions {
|
|
410
|
+
maxConcurrentJobs?: number;
|
|
411
|
+
retryDelayMs?: number;
|
|
412
|
+
maxAttempts?: number;
|
|
413
|
+
maxRecentCompletions?: number;
|
|
414
|
+
onAudit?: AuditCallback;
|
|
415
|
+
}
|
|
416
|
+
declare class AutomationScheduler {
|
|
417
|
+
private readonly maxConcurrentJobs;
|
|
418
|
+
private readonly retryDelayMs;
|
|
419
|
+
private readonly defaultMaxAttempts;
|
|
420
|
+
private readonly maxRecentCompletions;
|
|
421
|
+
private readonly onAudit;
|
|
422
|
+
/** Active jobs indexed by id. */
|
|
423
|
+
private readonly jobs;
|
|
424
|
+
/** dedupeKey → jobId for fast dedup lookups. */
|
|
425
|
+
private readonly dedupeIndex;
|
|
426
|
+
/** Ring buffer for completed/failed jobs. */
|
|
427
|
+
private readonly recentCompletions;
|
|
428
|
+
private pumpTimer;
|
|
429
|
+
private pumping;
|
|
430
|
+
constructor(options?: SchedulerOptions);
|
|
431
|
+
enqueue(opts: EnqueueOptions): EnqueueResult;
|
|
432
|
+
markCompleted(jobId: string): void;
|
|
433
|
+
markFailed(jobId: string, error: string): void;
|
|
434
|
+
getStatus(): SchedulerStatus;
|
|
435
|
+
shutdown(): void;
|
|
436
|
+
private pump;
|
|
437
|
+
private dispatch;
|
|
438
|
+
private finalize;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* GuardianSessionRegistry — session reuse for recurring automation.
|
|
443
|
+
*
|
|
444
|
+
* When a loop or supervisor trigger spawns a session, the registry
|
|
445
|
+
* remembers which Happy session ID was used. On subsequent runs,
|
|
446
|
+
* it resolves the existing session so the spawner can --resume it
|
|
447
|
+
* instead of creating a fresh session every iteration.
|
|
448
|
+
*
|
|
449
|
+
* Key hierarchy: loop:{loopId} > project:{projectId}
|
|
450
|
+
*
|
|
451
|
+
* In-memory only — no file persistence (daemon restart = fresh).
|
|
452
|
+
*/
|
|
453
|
+
interface GuardianEntry {
|
|
454
|
+
readonly key: string;
|
|
455
|
+
sessionId: string;
|
|
456
|
+
loopId?: string;
|
|
457
|
+
projectId?: string;
|
|
458
|
+
updatedAt: number;
|
|
459
|
+
}
|
|
460
|
+
interface ResolveInput {
|
|
461
|
+
loopId?: string;
|
|
462
|
+
projectId?: string;
|
|
463
|
+
}
|
|
464
|
+
declare class GuardianSessionRegistry {
|
|
465
|
+
private readonly entries;
|
|
466
|
+
/**
|
|
467
|
+
* Find an existing session to reuse.
|
|
468
|
+
* Tries loop key first, then project key.
|
|
469
|
+
*/
|
|
470
|
+
resolve(input: ResolveInput): string | null;
|
|
471
|
+
/**
|
|
472
|
+
* Remember a session for future reuse.
|
|
473
|
+
* Stores under both loop and project keys if available.
|
|
474
|
+
*/
|
|
475
|
+
remember(sessionId: string, input: ResolveInput): void;
|
|
476
|
+
/**
|
|
477
|
+
* Forget a specific session (e.g., after it exits).
|
|
478
|
+
*/
|
|
479
|
+
forgetSession(sessionId: string): number;
|
|
480
|
+
/**
|
|
481
|
+
* Forget all entries for a loop.
|
|
482
|
+
*/
|
|
483
|
+
forgetLoop(loopId: string): boolean;
|
|
484
|
+
/**
|
|
485
|
+
* Get all entries (for observability).
|
|
486
|
+
*/
|
|
487
|
+
getSnapshot(): GuardianEntry[];
|
|
488
|
+
/**
|
|
489
|
+
* Number of tracked guardian entries.
|
|
490
|
+
*/
|
|
491
|
+
get size(): number;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* AgentLoopCoordinator — lightweight loop scheduler for agent daemon.
|
|
496
|
+
*
|
|
497
|
+
* A "loop" is a recurring autonomous task: run a prompt in a directory
|
|
498
|
+
* at a fixed interval. The coordinator polls every second, enqueues
|
|
499
|
+
* due loops into the AutomationScheduler, and tracks iteration state.
|
|
500
|
+
*
|
|
501
|
+
* Simplified from CLI's full coordinator — no cron, quiet hours,
|
|
502
|
+
* downstream chains, notifications, or memory snapshots.
|
|
503
|
+
*/
|
|
504
|
+
|
|
505
|
+
type LoopState = "idle" | "active" | "paused" | "blocked";
|
|
506
|
+
interface AgentLoop {
|
|
507
|
+
readonly id: string;
|
|
508
|
+
readonly name: string;
|
|
509
|
+
readonly prompt: string;
|
|
510
|
+
readonly directory: string;
|
|
511
|
+
readonly intervalMs: number;
|
|
512
|
+
readonly createdAt: number;
|
|
513
|
+
state: LoopState;
|
|
514
|
+
iteration: number;
|
|
515
|
+
nextRunAt: number;
|
|
516
|
+
lastStartedAt?: number;
|
|
517
|
+
lastCompletedAt?: number;
|
|
518
|
+
activeJobId?: string;
|
|
519
|
+
consecutiveFailures: number;
|
|
520
|
+
maxConsecutiveFailures: number;
|
|
521
|
+
maxIterations: number;
|
|
522
|
+
errorMessage?: string;
|
|
523
|
+
}
|
|
524
|
+
interface CreateLoopInput {
|
|
525
|
+
name: string;
|
|
526
|
+
prompt: string;
|
|
527
|
+
directory: string;
|
|
528
|
+
intervalMs: number;
|
|
529
|
+
maxConsecutiveFailures?: number;
|
|
530
|
+
maxIterations?: number;
|
|
531
|
+
}
|
|
532
|
+
interface LoopSummary {
|
|
533
|
+
id: string;
|
|
534
|
+
name: string;
|
|
535
|
+
state: LoopState;
|
|
536
|
+
iteration: number;
|
|
537
|
+
intervalMs: number;
|
|
538
|
+
nextRunAt: number;
|
|
539
|
+
lastCompletedAt?: number;
|
|
540
|
+
}
|
|
541
|
+
declare class AgentLoopCoordinator {
|
|
542
|
+
private readonly loops;
|
|
543
|
+
private readonly scheduler;
|
|
544
|
+
private readonly serverUrl;
|
|
545
|
+
private readonly authToken;
|
|
546
|
+
private readonly guardian;
|
|
547
|
+
private tickTimer;
|
|
548
|
+
constructor(scheduler: AutomationScheduler, serverUrl: string, authToken: string, guardian?: GuardianSessionRegistry);
|
|
549
|
+
start(): void;
|
|
550
|
+
shutdown(): void;
|
|
551
|
+
createLoop(input: CreateLoopInput): AgentLoop;
|
|
552
|
+
getLoop(id: string): AgentLoop | undefined;
|
|
553
|
+
listLoops(): LoopSummary[];
|
|
554
|
+
pauseLoop(id: string): boolean;
|
|
555
|
+
resumeLoop(id: string): boolean;
|
|
556
|
+
deleteLoop(id: string): boolean;
|
|
557
|
+
onJobTerminal(loopId: string, status: "completed" | "failed", errorMessage?: string): void;
|
|
558
|
+
private tick;
|
|
559
|
+
private enqueueLoop;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* AutomationAuditStore — in-memory ring buffer for automation events.
|
|
564
|
+
*
|
|
565
|
+
* Records job lifecycle events (enqueued, started, completed, failed)
|
|
566
|
+
* for observability. Capped at maxEntries to prevent unbounded growth.
|
|
567
|
+
* Queryable via RPC for dashboard / debugging.
|
|
568
|
+
*/
|
|
569
|
+
type AuditEventKind = "job_enqueued" | "job_dispatched" | "job_completed" | "job_failed" | "job_retried" | "loop_started" | "loop_blocked" | "loop_paused";
|
|
570
|
+
interface AuditEvent {
|
|
571
|
+
readonly id: number;
|
|
572
|
+
readonly kind: AuditEventKind;
|
|
573
|
+
readonly timestamp: number;
|
|
574
|
+
readonly jobId?: string;
|
|
575
|
+
readonly dedupeKey?: string;
|
|
576
|
+
readonly loopId?: string;
|
|
577
|
+
readonly loopName?: string;
|
|
578
|
+
readonly status?: string;
|
|
579
|
+
readonly message?: string;
|
|
580
|
+
readonly errorMessage?: string;
|
|
581
|
+
}
|
|
582
|
+
interface AuditQuery {
|
|
583
|
+
kind?: AuditEventKind;
|
|
584
|
+
loopId?: string;
|
|
585
|
+
limit?: number;
|
|
586
|
+
since?: number;
|
|
587
|
+
}
|
|
588
|
+
interface AuditStorOptions {
|
|
589
|
+
maxEntries?: number;
|
|
590
|
+
}
|
|
591
|
+
declare class AutomationAuditStore {
|
|
592
|
+
private readonly maxEntries;
|
|
593
|
+
private readonly events;
|
|
594
|
+
private nextId;
|
|
595
|
+
constructor(options?: AuditStorOptions);
|
|
596
|
+
/**
|
|
597
|
+
* Record an audit event.
|
|
598
|
+
*/
|
|
599
|
+
record(event: Omit<AuditEvent, "id" | "timestamp">): AuditEvent;
|
|
600
|
+
/**
|
|
601
|
+
* Query events with optional filters.
|
|
602
|
+
*/
|
|
603
|
+
query(filter?: AuditQuery): AuditEvent[];
|
|
604
|
+
/**
|
|
605
|
+
* Summary counts by kind.
|
|
606
|
+
*/
|
|
607
|
+
summarize(): Record<AuditEventKind, number>;
|
|
608
|
+
/**
|
|
609
|
+
* Total number of stored events.
|
|
610
|
+
*/
|
|
611
|
+
get size(): number;
|
|
612
|
+
}
|
|
613
|
+
|
|
348
614
|
/**
|
|
349
615
|
* Machine WebSocket client — trimmed from CLI's ApiMachineClient.
|
|
350
616
|
*
|
|
@@ -431,8 +697,13 @@ declare class MachineClient {
|
|
|
431
697
|
private automationEnabled;
|
|
432
698
|
private automationServerUrl;
|
|
433
699
|
private automationAuthToken;
|
|
700
|
+
private scheduler;
|
|
701
|
+
private loopCoordinator;
|
|
702
|
+
private auditStore;
|
|
434
703
|
constructor(opts: MachineClientOptions);
|
|
435
704
|
private registerMachineHandlers;
|
|
705
|
+
private registerLoopHandlers;
|
|
706
|
+
private registerAuditHandlers;
|
|
436
707
|
connect(): void;
|
|
437
708
|
updateMachineMetadata(handler: (metadata: MachineMetadata | null) => MachineMetadata): Promise<void>;
|
|
438
709
|
updateDaemonState(handler: (state: DaemonState | null) => DaemonState): Promise<void>;
|
|
@@ -494,7 +765,7 @@ declare class MachineClient {
|
|
|
494
765
|
* Enable automation handling — agent will process webhook, supervisor,
|
|
495
766
|
* and task triggers from the server by spawning Happy CLI sessions.
|
|
496
767
|
*/
|
|
497
|
-
enableAutomation(serverUrl: string, authToken: string): void;
|
|
768
|
+
enableAutomation(serverUrl: string, authToken: string, scheduler: AutomationScheduler, loopCoordinator?: AgentLoopCoordinator, auditStore?: AutomationAuditStore): void;
|
|
498
769
|
/** Internal dispatch for ephemeral events that need automation handling. */
|
|
499
770
|
private handleAutomationEvent;
|
|
500
771
|
/** Seed initial Tailscale info detected before connect. */
|
package/dist/index.d.mts
CHANGED
|
@@ -345,6 +345,272 @@ declare class TunnelManager {
|
|
|
345
345
|
stopRefresh(): void;
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
+
/**
|
|
349
|
+
* AutomationScheduler — lightweight in-memory job queue for agent triggers.
|
|
350
|
+
*
|
|
351
|
+
* Features:
|
|
352
|
+
* - Priority queue (urgent > user > background)
|
|
353
|
+
* - dedupeKey deduplication (queued/dispatching/running)
|
|
354
|
+
* - Concurrency limit (maxConcurrentJobs)
|
|
355
|
+
* - Retry with incremental backoff (attempt * retryDelayMs)
|
|
356
|
+
* - Ring buffer for recent completions (observability)
|
|
357
|
+
*/
|
|
358
|
+
type JobPriority = "urgent" | "user" | "background";
|
|
359
|
+
type JobStatus = "queued" | "dispatching" | "running" | "completed" | "failed";
|
|
360
|
+
interface SchedulerJob {
|
|
361
|
+
readonly id: string;
|
|
362
|
+
readonly kind: "webhook" | "supervisor" | "task";
|
|
363
|
+
readonly dedupeKey: string;
|
|
364
|
+
readonly priority: JobPriority;
|
|
365
|
+
status: JobStatus;
|
|
366
|
+
attempt: number;
|
|
367
|
+
readonly maxAttempts: number;
|
|
368
|
+
readonly createdAt: number;
|
|
369
|
+
updatedAt: number;
|
|
370
|
+
nextRunAt: number;
|
|
371
|
+
errorMessage?: string;
|
|
372
|
+
pid?: number;
|
|
373
|
+
/** The thunk that performs the actual work (spawn session etc.) */
|
|
374
|
+
readonly run: (jobId: string) => Promise<{
|
|
375
|
+
pid: number;
|
|
376
|
+
}>;
|
|
377
|
+
}
|
|
378
|
+
interface EnqueueOptions {
|
|
379
|
+
kind: SchedulerJob["kind"];
|
|
380
|
+
dedupeKey: string;
|
|
381
|
+
priority: JobPriority;
|
|
382
|
+
run: (jobId: string) => Promise<{
|
|
383
|
+
pid: number;
|
|
384
|
+
}>;
|
|
385
|
+
}
|
|
386
|
+
interface EnqueueResult {
|
|
387
|
+
job: SchedulerJob;
|
|
388
|
+
deduped: boolean;
|
|
389
|
+
}
|
|
390
|
+
interface SchedulerStatus {
|
|
391
|
+
queueLength: number;
|
|
392
|
+
runningCount: number;
|
|
393
|
+
recentCompletions: Array<{
|
|
394
|
+
id: string;
|
|
395
|
+
kind: string;
|
|
396
|
+
dedupeKey: string;
|
|
397
|
+
status: "completed" | "failed";
|
|
398
|
+
completedAt: number;
|
|
399
|
+
errorMessage?: string;
|
|
400
|
+
}>;
|
|
401
|
+
}
|
|
402
|
+
type AuditCallback = (event: {
|
|
403
|
+
kind: "job_enqueued" | "job_dispatched" | "job_completed" | "job_failed" | "job_retried";
|
|
404
|
+
jobId: string;
|
|
405
|
+
dedupeKey: string;
|
|
406
|
+
message?: string;
|
|
407
|
+
errorMessage?: string;
|
|
408
|
+
}) => void;
|
|
409
|
+
interface SchedulerOptions {
|
|
410
|
+
maxConcurrentJobs?: number;
|
|
411
|
+
retryDelayMs?: number;
|
|
412
|
+
maxAttempts?: number;
|
|
413
|
+
maxRecentCompletions?: number;
|
|
414
|
+
onAudit?: AuditCallback;
|
|
415
|
+
}
|
|
416
|
+
declare class AutomationScheduler {
|
|
417
|
+
private readonly maxConcurrentJobs;
|
|
418
|
+
private readonly retryDelayMs;
|
|
419
|
+
private readonly defaultMaxAttempts;
|
|
420
|
+
private readonly maxRecentCompletions;
|
|
421
|
+
private readonly onAudit;
|
|
422
|
+
/** Active jobs indexed by id. */
|
|
423
|
+
private readonly jobs;
|
|
424
|
+
/** dedupeKey → jobId for fast dedup lookups. */
|
|
425
|
+
private readonly dedupeIndex;
|
|
426
|
+
/** Ring buffer for completed/failed jobs. */
|
|
427
|
+
private readonly recentCompletions;
|
|
428
|
+
private pumpTimer;
|
|
429
|
+
private pumping;
|
|
430
|
+
constructor(options?: SchedulerOptions);
|
|
431
|
+
enqueue(opts: EnqueueOptions): EnqueueResult;
|
|
432
|
+
markCompleted(jobId: string): void;
|
|
433
|
+
markFailed(jobId: string, error: string): void;
|
|
434
|
+
getStatus(): SchedulerStatus;
|
|
435
|
+
shutdown(): void;
|
|
436
|
+
private pump;
|
|
437
|
+
private dispatch;
|
|
438
|
+
private finalize;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* GuardianSessionRegistry — session reuse for recurring automation.
|
|
443
|
+
*
|
|
444
|
+
* When a loop or supervisor trigger spawns a session, the registry
|
|
445
|
+
* remembers which Happy session ID was used. On subsequent runs,
|
|
446
|
+
* it resolves the existing session so the spawner can --resume it
|
|
447
|
+
* instead of creating a fresh session every iteration.
|
|
448
|
+
*
|
|
449
|
+
* Key hierarchy: loop:{loopId} > project:{projectId}
|
|
450
|
+
*
|
|
451
|
+
* In-memory only — no file persistence (daemon restart = fresh).
|
|
452
|
+
*/
|
|
453
|
+
interface GuardianEntry {
|
|
454
|
+
readonly key: string;
|
|
455
|
+
sessionId: string;
|
|
456
|
+
loopId?: string;
|
|
457
|
+
projectId?: string;
|
|
458
|
+
updatedAt: number;
|
|
459
|
+
}
|
|
460
|
+
interface ResolveInput {
|
|
461
|
+
loopId?: string;
|
|
462
|
+
projectId?: string;
|
|
463
|
+
}
|
|
464
|
+
declare class GuardianSessionRegistry {
|
|
465
|
+
private readonly entries;
|
|
466
|
+
/**
|
|
467
|
+
* Find an existing session to reuse.
|
|
468
|
+
* Tries loop key first, then project key.
|
|
469
|
+
*/
|
|
470
|
+
resolve(input: ResolveInput): string | null;
|
|
471
|
+
/**
|
|
472
|
+
* Remember a session for future reuse.
|
|
473
|
+
* Stores under both loop and project keys if available.
|
|
474
|
+
*/
|
|
475
|
+
remember(sessionId: string, input: ResolveInput): void;
|
|
476
|
+
/**
|
|
477
|
+
* Forget a specific session (e.g., after it exits).
|
|
478
|
+
*/
|
|
479
|
+
forgetSession(sessionId: string): number;
|
|
480
|
+
/**
|
|
481
|
+
* Forget all entries for a loop.
|
|
482
|
+
*/
|
|
483
|
+
forgetLoop(loopId: string): boolean;
|
|
484
|
+
/**
|
|
485
|
+
* Get all entries (for observability).
|
|
486
|
+
*/
|
|
487
|
+
getSnapshot(): GuardianEntry[];
|
|
488
|
+
/**
|
|
489
|
+
* Number of tracked guardian entries.
|
|
490
|
+
*/
|
|
491
|
+
get size(): number;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* AgentLoopCoordinator — lightweight loop scheduler for agent daemon.
|
|
496
|
+
*
|
|
497
|
+
* A "loop" is a recurring autonomous task: run a prompt in a directory
|
|
498
|
+
* at a fixed interval. The coordinator polls every second, enqueues
|
|
499
|
+
* due loops into the AutomationScheduler, and tracks iteration state.
|
|
500
|
+
*
|
|
501
|
+
* Simplified from CLI's full coordinator — no cron, quiet hours,
|
|
502
|
+
* downstream chains, notifications, or memory snapshots.
|
|
503
|
+
*/
|
|
504
|
+
|
|
505
|
+
type LoopState = "idle" | "active" | "paused" | "blocked";
|
|
506
|
+
interface AgentLoop {
|
|
507
|
+
readonly id: string;
|
|
508
|
+
readonly name: string;
|
|
509
|
+
readonly prompt: string;
|
|
510
|
+
readonly directory: string;
|
|
511
|
+
readonly intervalMs: number;
|
|
512
|
+
readonly createdAt: number;
|
|
513
|
+
state: LoopState;
|
|
514
|
+
iteration: number;
|
|
515
|
+
nextRunAt: number;
|
|
516
|
+
lastStartedAt?: number;
|
|
517
|
+
lastCompletedAt?: number;
|
|
518
|
+
activeJobId?: string;
|
|
519
|
+
consecutiveFailures: number;
|
|
520
|
+
maxConsecutiveFailures: number;
|
|
521
|
+
maxIterations: number;
|
|
522
|
+
errorMessage?: string;
|
|
523
|
+
}
|
|
524
|
+
interface CreateLoopInput {
|
|
525
|
+
name: string;
|
|
526
|
+
prompt: string;
|
|
527
|
+
directory: string;
|
|
528
|
+
intervalMs: number;
|
|
529
|
+
maxConsecutiveFailures?: number;
|
|
530
|
+
maxIterations?: number;
|
|
531
|
+
}
|
|
532
|
+
interface LoopSummary {
|
|
533
|
+
id: string;
|
|
534
|
+
name: string;
|
|
535
|
+
state: LoopState;
|
|
536
|
+
iteration: number;
|
|
537
|
+
intervalMs: number;
|
|
538
|
+
nextRunAt: number;
|
|
539
|
+
lastCompletedAt?: number;
|
|
540
|
+
}
|
|
541
|
+
declare class AgentLoopCoordinator {
|
|
542
|
+
private readonly loops;
|
|
543
|
+
private readonly scheduler;
|
|
544
|
+
private readonly serverUrl;
|
|
545
|
+
private readonly authToken;
|
|
546
|
+
private readonly guardian;
|
|
547
|
+
private tickTimer;
|
|
548
|
+
constructor(scheduler: AutomationScheduler, serverUrl: string, authToken: string, guardian?: GuardianSessionRegistry);
|
|
549
|
+
start(): void;
|
|
550
|
+
shutdown(): void;
|
|
551
|
+
createLoop(input: CreateLoopInput): AgentLoop;
|
|
552
|
+
getLoop(id: string): AgentLoop | undefined;
|
|
553
|
+
listLoops(): LoopSummary[];
|
|
554
|
+
pauseLoop(id: string): boolean;
|
|
555
|
+
resumeLoop(id: string): boolean;
|
|
556
|
+
deleteLoop(id: string): boolean;
|
|
557
|
+
onJobTerminal(loopId: string, status: "completed" | "failed", errorMessage?: string): void;
|
|
558
|
+
private tick;
|
|
559
|
+
private enqueueLoop;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* AutomationAuditStore — in-memory ring buffer for automation events.
|
|
564
|
+
*
|
|
565
|
+
* Records job lifecycle events (enqueued, started, completed, failed)
|
|
566
|
+
* for observability. Capped at maxEntries to prevent unbounded growth.
|
|
567
|
+
* Queryable via RPC for dashboard / debugging.
|
|
568
|
+
*/
|
|
569
|
+
type AuditEventKind = "job_enqueued" | "job_dispatched" | "job_completed" | "job_failed" | "job_retried" | "loop_started" | "loop_blocked" | "loop_paused";
|
|
570
|
+
interface AuditEvent {
|
|
571
|
+
readonly id: number;
|
|
572
|
+
readonly kind: AuditEventKind;
|
|
573
|
+
readonly timestamp: number;
|
|
574
|
+
readonly jobId?: string;
|
|
575
|
+
readonly dedupeKey?: string;
|
|
576
|
+
readonly loopId?: string;
|
|
577
|
+
readonly loopName?: string;
|
|
578
|
+
readonly status?: string;
|
|
579
|
+
readonly message?: string;
|
|
580
|
+
readonly errorMessage?: string;
|
|
581
|
+
}
|
|
582
|
+
interface AuditQuery {
|
|
583
|
+
kind?: AuditEventKind;
|
|
584
|
+
loopId?: string;
|
|
585
|
+
limit?: number;
|
|
586
|
+
since?: number;
|
|
587
|
+
}
|
|
588
|
+
interface AuditStorOptions {
|
|
589
|
+
maxEntries?: number;
|
|
590
|
+
}
|
|
591
|
+
declare class AutomationAuditStore {
|
|
592
|
+
private readonly maxEntries;
|
|
593
|
+
private readonly events;
|
|
594
|
+
private nextId;
|
|
595
|
+
constructor(options?: AuditStorOptions);
|
|
596
|
+
/**
|
|
597
|
+
* Record an audit event.
|
|
598
|
+
*/
|
|
599
|
+
record(event: Omit<AuditEvent, "id" | "timestamp">): AuditEvent;
|
|
600
|
+
/**
|
|
601
|
+
* Query events with optional filters.
|
|
602
|
+
*/
|
|
603
|
+
query(filter?: AuditQuery): AuditEvent[];
|
|
604
|
+
/**
|
|
605
|
+
* Summary counts by kind.
|
|
606
|
+
*/
|
|
607
|
+
summarize(): Record<AuditEventKind, number>;
|
|
608
|
+
/**
|
|
609
|
+
* Total number of stored events.
|
|
610
|
+
*/
|
|
611
|
+
get size(): number;
|
|
612
|
+
}
|
|
613
|
+
|
|
348
614
|
/**
|
|
349
615
|
* Machine WebSocket client — trimmed from CLI's ApiMachineClient.
|
|
350
616
|
*
|
|
@@ -431,8 +697,13 @@ declare class MachineClient {
|
|
|
431
697
|
private automationEnabled;
|
|
432
698
|
private automationServerUrl;
|
|
433
699
|
private automationAuthToken;
|
|
700
|
+
private scheduler;
|
|
701
|
+
private loopCoordinator;
|
|
702
|
+
private auditStore;
|
|
434
703
|
constructor(opts: MachineClientOptions);
|
|
435
704
|
private registerMachineHandlers;
|
|
705
|
+
private registerLoopHandlers;
|
|
706
|
+
private registerAuditHandlers;
|
|
436
707
|
connect(): void;
|
|
437
708
|
updateMachineMetadata(handler: (metadata: MachineMetadata | null) => MachineMetadata): Promise<void>;
|
|
438
709
|
updateDaemonState(handler: (state: DaemonState | null) => DaemonState): Promise<void>;
|
|
@@ -494,7 +765,7 @@ declare class MachineClient {
|
|
|
494
765
|
* Enable automation handling — agent will process webhook, supervisor,
|
|
495
766
|
* and task triggers from the server by spawning Happy CLI sessions.
|
|
496
767
|
*/
|
|
497
|
-
enableAutomation(serverUrl: string, authToken: string): void;
|
|
768
|
+
enableAutomation(serverUrl: string, authToken: string, scheduler: AutomationScheduler, loopCoordinator?: AgentLoopCoordinator, auditStore?: AutomationAuditStore): void;
|
|
498
769
|
/** Internal dispatch for ephemeral events that need automation handling. */
|
|
499
770
|
private handleAutomationEvent;
|
|
500
771
|
/** Seed initial Tailscale info detected before connect. */
|