@kmmao/happy-agent 0.3.11 → 0.4.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.cts CHANGED
@@ -345,6 +345,90 @@ 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
+ interface SchedulerOptions {
403
+ maxConcurrentJobs?: number;
404
+ retryDelayMs?: number;
405
+ maxAttempts?: number;
406
+ maxRecentCompletions?: number;
407
+ }
408
+ declare class AutomationScheduler {
409
+ private readonly maxConcurrentJobs;
410
+ private readonly retryDelayMs;
411
+ private readonly defaultMaxAttempts;
412
+ private readonly maxRecentCompletions;
413
+ /** Active jobs indexed by id. */
414
+ private readonly jobs;
415
+ /** dedupeKey → jobId for fast dedup lookups. */
416
+ private readonly dedupeIndex;
417
+ /** Ring buffer for completed/failed jobs. */
418
+ private readonly recentCompletions;
419
+ private pumpTimer;
420
+ private pumping;
421
+ constructor(options?: SchedulerOptions);
422
+ enqueue(opts: EnqueueOptions): EnqueueResult;
423
+ markCompleted(jobId: string): void;
424
+ markFailed(jobId: string, error: string): void;
425
+ getStatus(): SchedulerStatus;
426
+ shutdown(): void;
427
+ private pump;
428
+ private dispatch;
429
+ private finalize;
430
+ }
431
+
348
432
  /**
349
433
  * Machine WebSocket client — trimmed from CLI's ApiMachineClient.
350
434
  *
@@ -361,17 +445,59 @@ declare class TunnelManager {
361
445
  * - CLI-specific logging (debugLargeJson)
362
446
  */
363
447
 
448
+ /** Strongly-typed ephemeral event from server. */
449
+ type EphemeralEvent = {
450
+ type: "activity";
451
+ id: string;
452
+ active: boolean;
453
+ activeAt: number;
454
+ thinking: boolean;
455
+ } | {
456
+ type: "webhook-trigger";
457
+ webhookEventId: string;
458
+ issueNumber: number;
459
+ issueTitle: string;
460
+ issueBody: string;
461
+ issueAuthor: string;
462
+ issueLabels: string[];
463
+ issueUrl: string;
464
+ repoUrl: string;
465
+ repoPath: string;
466
+ provider: string;
467
+ } | {
468
+ type: "supervisor-trigger";
469
+ projectId: string;
470
+ runId: string;
471
+ trigger: string;
472
+ machineId: string;
473
+ repoPath: string;
474
+ mode?: string;
475
+ dimensions?: string[];
476
+ changedFiles?: string[];
477
+ customRules?: string;
478
+ } | {
479
+ type: "task-trigger";
480
+ taskId: string;
481
+ prompt: string;
482
+ directory: string;
483
+ priority: string;
484
+ projectId?: string;
485
+ resultToken?: string;
486
+ skillContents?: Array<{
487
+ name: string;
488
+ content: string;
489
+ }>;
490
+ };
364
491
  type MachineClientOptions = {
365
492
  readonly token: string;
366
493
  readonly machine: Machine;
367
494
  readonly serverUrl: string;
495
+ /** Agent package version for DaemonState reporting. */
496
+ readonly agentVersion?: string;
368
497
  /** Working directory for RPC handlers. Defaults to process.cwd(). */
369
498
  readonly workingDirectory?: string;
370
499
  /** Handler for ephemeral events from server. */
371
- readonly onEphemeral?: (data: {
372
- type: string;
373
- [key: string]: unknown;
374
- }) => void;
500
+ readonly onEphemeral?: (data: EphemeralEvent) => void;
375
501
  };
376
502
  declare class MachineClient {
377
503
  readonly machine: Machine;
@@ -383,11 +509,79 @@ declare class MachineClient {
383
509
  private tunnelManager;
384
510
  private readonly token;
385
511
  private readonly serverUrl;
512
+ private readonly agentVersion;
513
+ private readonly startTime;
386
514
  private readonly onEphemeral?;
515
+ private automationEnabled;
516
+ private automationServerUrl;
517
+ private automationAuthToken;
518
+ private scheduler;
387
519
  constructor(opts: MachineClientOptions);
520
+ private registerMachineHandlers;
388
521
  connect(): void;
389
522
  updateMachineMetadata(handler: (metadata: MachineMetadata | null) => MachineMetadata): Promise<void>;
390
523
  updateDaemonState(handler: (state: DaemonState | null) => DaemonState): Promise<void>;
524
+ /** Emit a session lifecycle event. */
525
+ emitSessionEvent(sessionId: string, eventType: string, summary: string, detail?: Record<string, unknown>): void;
526
+ /** Report webhook processing status. */
527
+ emitWebhookStatus(data: {
528
+ webhookEventId: string;
529
+ status: "dispatched" | "completed" | "failed";
530
+ sessionId?: string;
531
+ errorMessage?: string;
532
+ }): void;
533
+ /** Report supervisor run status. */
534
+ emitSupervisorRunStatus(data: {
535
+ runId: string;
536
+ projectId: string;
537
+ status: "running" | "completed" | "failed";
538
+ sessionId?: string;
539
+ actionsCount?: number;
540
+ issuesCreated?: number;
541
+ errorMessage?: string;
542
+ }): void;
543
+ /** Submit a knowledge entry from a session. */
544
+ emitSubmitKnowledge(sid: string, entry: {
545
+ entryType: string;
546
+ contributorType: string;
547
+ action: string;
548
+ title: string;
549
+ content: string;
550
+ request?: string;
551
+ outcome?: string;
552
+ tags: string[];
553
+ confidence: string;
554
+ model?: string;
555
+ affectedFiles: string[];
556
+ }): void;
557
+ /** Fetch knowledge for a session. */
558
+ emitFetchKnowledge(sid: string, mode: "auto" | "full" | "minimal", contextHints: string[] | undefined, callback: (response: {
559
+ profile: {
560
+ techStack: string[];
561
+ architectureType?: string;
562
+ knownPitfalls: string[];
563
+ coreConventions: string[];
564
+ lastUpdatedAt: number;
565
+ } | null;
566
+ entries: {
567
+ id: string;
568
+ entryType: string;
569
+ title: string;
570
+ content: string;
571
+ tags: string[];
572
+ confidence: string;
573
+ createdAt: string;
574
+ }[];
575
+ }) => void): void;
576
+ /** Stream task log chunk. */
577
+ emitTaskLog(sid: string, taskId: string, outputFile: string, chunk: string, offset: number): void;
578
+ /**
579
+ * Enable automation handling — agent will process webhook, supervisor,
580
+ * and task triggers from the server by spawning Happy CLI sessions.
581
+ */
582
+ enableAutomation(serverUrl: string, authToken: string, scheduler: AutomationScheduler): void;
583
+ /** Internal dispatch for ephemeral events that need automation handling. */
584
+ private handleAutomationEvent;
391
585
  /** Seed initial Tailscale info detected before connect. */
392
586
  setTailscaleInfo(info: TailscaleInfo): void;
393
587
  /** Attach a TunnelManager for periodic tunnel state refresh. */
@@ -399,5 +593,26 @@ declare class MachineClient {
399
593
  private stopTailscaleRefresh;
400
594
  }
401
595
 
402
- export { MachineClient, RpcHandlerManager, SessionClient, authLogin, authLogout, authStatus, createRpcHandlerManager, createSession, deleteSession, fetchMessagesAfterSeq, getOrCreateMachine, getSessionMessages, listActiveSessions, listMachines, listSessions, loadConfig, readCredentials, requireCredentials, resolveSessionEncryption, sendMessagesBatch };
403
- export type { Config, Credentials, DecryptedMessage, DecryptedSession, EncryptionVariant, MachineClientOptions, RpcHandler, RpcHandlerConfig, SessionClientOptions, SessionEncryption };
596
+ /**
597
+ * Daemon mode persistent background process that connects to the server,
598
+ * registers RPC handlers, and processes automation triggers.
599
+ *
600
+ * Usage: happy-agent daemon start [--directory <dir>]
601
+ *
602
+ * The daemon:
603
+ * 1. Loads credentials and config
604
+ * 2. Registers/gets machine identity
605
+ * 3. Detects Tailscale and tunnel state
606
+ * 4. Creates MachineClient with all RPC handlers
607
+ * 5. Enables automation (webhook/supervisor/task triggers)
608
+ * 6. Runs until SIGTERM/SIGINT
609
+ */
610
+ declare function startDaemon(options: {
611
+ directory?: string;
612
+ foreground?: boolean;
613
+ }): Promise<void>;
614
+ declare function stopDaemon(): void;
615
+ declare function daemonStatus(): void;
616
+
617
+ export { MachineClient, RpcHandlerManager, SessionClient, authLogin, authLogout, authStatus, createRpcHandlerManager, createSession, daemonStatus, deleteSession, fetchMessagesAfterSeq, getOrCreateMachine, getSessionMessages, listActiveSessions, listMachines, listSessions, loadConfig, readCredentials, requireCredentials, resolveSessionEncryption, sendMessagesBatch, startDaemon, stopDaemon };
618
+ export type { Config, Credentials, DecryptedMessage, DecryptedSession, EncryptionVariant, EphemeralEvent, MachineClientOptions, RpcHandler, RpcHandlerConfig, SessionClientOptions, SessionEncryption };
package/dist/index.d.mts CHANGED
@@ -345,6 +345,90 @@ 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
+ interface SchedulerOptions {
403
+ maxConcurrentJobs?: number;
404
+ retryDelayMs?: number;
405
+ maxAttempts?: number;
406
+ maxRecentCompletions?: number;
407
+ }
408
+ declare class AutomationScheduler {
409
+ private readonly maxConcurrentJobs;
410
+ private readonly retryDelayMs;
411
+ private readonly defaultMaxAttempts;
412
+ private readonly maxRecentCompletions;
413
+ /** Active jobs indexed by id. */
414
+ private readonly jobs;
415
+ /** dedupeKey → jobId for fast dedup lookups. */
416
+ private readonly dedupeIndex;
417
+ /** Ring buffer for completed/failed jobs. */
418
+ private readonly recentCompletions;
419
+ private pumpTimer;
420
+ private pumping;
421
+ constructor(options?: SchedulerOptions);
422
+ enqueue(opts: EnqueueOptions): EnqueueResult;
423
+ markCompleted(jobId: string): void;
424
+ markFailed(jobId: string, error: string): void;
425
+ getStatus(): SchedulerStatus;
426
+ shutdown(): void;
427
+ private pump;
428
+ private dispatch;
429
+ private finalize;
430
+ }
431
+
348
432
  /**
349
433
  * Machine WebSocket client — trimmed from CLI's ApiMachineClient.
350
434
  *
@@ -361,17 +445,59 @@ declare class TunnelManager {
361
445
  * - CLI-specific logging (debugLargeJson)
362
446
  */
363
447
 
448
+ /** Strongly-typed ephemeral event from server. */
449
+ type EphemeralEvent = {
450
+ type: "activity";
451
+ id: string;
452
+ active: boolean;
453
+ activeAt: number;
454
+ thinking: boolean;
455
+ } | {
456
+ type: "webhook-trigger";
457
+ webhookEventId: string;
458
+ issueNumber: number;
459
+ issueTitle: string;
460
+ issueBody: string;
461
+ issueAuthor: string;
462
+ issueLabels: string[];
463
+ issueUrl: string;
464
+ repoUrl: string;
465
+ repoPath: string;
466
+ provider: string;
467
+ } | {
468
+ type: "supervisor-trigger";
469
+ projectId: string;
470
+ runId: string;
471
+ trigger: string;
472
+ machineId: string;
473
+ repoPath: string;
474
+ mode?: string;
475
+ dimensions?: string[];
476
+ changedFiles?: string[];
477
+ customRules?: string;
478
+ } | {
479
+ type: "task-trigger";
480
+ taskId: string;
481
+ prompt: string;
482
+ directory: string;
483
+ priority: string;
484
+ projectId?: string;
485
+ resultToken?: string;
486
+ skillContents?: Array<{
487
+ name: string;
488
+ content: string;
489
+ }>;
490
+ };
364
491
  type MachineClientOptions = {
365
492
  readonly token: string;
366
493
  readonly machine: Machine;
367
494
  readonly serverUrl: string;
495
+ /** Agent package version for DaemonState reporting. */
496
+ readonly agentVersion?: string;
368
497
  /** Working directory for RPC handlers. Defaults to process.cwd(). */
369
498
  readonly workingDirectory?: string;
370
499
  /** Handler for ephemeral events from server. */
371
- readonly onEphemeral?: (data: {
372
- type: string;
373
- [key: string]: unknown;
374
- }) => void;
500
+ readonly onEphemeral?: (data: EphemeralEvent) => void;
375
501
  };
376
502
  declare class MachineClient {
377
503
  readonly machine: Machine;
@@ -383,11 +509,79 @@ declare class MachineClient {
383
509
  private tunnelManager;
384
510
  private readonly token;
385
511
  private readonly serverUrl;
512
+ private readonly agentVersion;
513
+ private readonly startTime;
386
514
  private readonly onEphemeral?;
515
+ private automationEnabled;
516
+ private automationServerUrl;
517
+ private automationAuthToken;
518
+ private scheduler;
387
519
  constructor(opts: MachineClientOptions);
520
+ private registerMachineHandlers;
388
521
  connect(): void;
389
522
  updateMachineMetadata(handler: (metadata: MachineMetadata | null) => MachineMetadata): Promise<void>;
390
523
  updateDaemonState(handler: (state: DaemonState | null) => DaemonState): Promise<void>;
524
+ /** Emit a session lifecycle event. */
525
+ emitSessionEvent(sessionId: string, eventType: string, summary: string, detail?: Record<string, unknown>): void;
526
+ /** Report webhook processing status. */
527
+ emitWebhookStatus(data: {
528
+ webhookEventId: string;
529
+ status: "dispatched" | "completed" | "failed";
530
+ sessionId?: string;
531
+ errorMessage?: string;
532
+ }): void;
533
+ /** Report supervisor run status. */
534
+ emitSupervisorRunStatus(data: {
535
+ runId: string;
536
+ projectId: string;
537
+ status: "running" | "completed" | "failed";
538
+ sessionId?: string;
539
+ actionsCount?: number;
540
+ issuesCreated?: number;
541
+ errorMessage?: string;
542
+ }): void;
543
+ /** Submit a knowledge entry from a session. */
544
+ emitSubmitKnowledge(sid: string, entry: {
545
+ entryType: string;
546
+ contributorType: string;
547
+ action: string;
548
+ title: string;
549
+ content: string;
550
+ request?: string;
551
+ outcome?: string;
552
+ tags: string[];
553
+ confidence: string;
554
+ model?: string;
555
+ affectedFiles: string[];
556
+ }): void;
557
+ /** Fetch knowledge for a session. */
558
+ emitFetchKnowledge(sid: string, mode: "auto" | "full" | "minimal", contextHints: string[] | undefined, callback: (response: {
559
+ profile: {
560
+ techStack: string[];
561
+ architectureType?: string;
562
+ knownPitfalls: string[];
563
+ coreConventions: string[];
564
+ lastUpdatedAt: number;
565
+ } | null;
566
+ entries: {
567
+ id: string;
568
+ entryType: string;
569
+ title: string;
570
+ content: string;
571
+ tags: string[];
572
+ confidence: string;
573
+ createdAt: string;
574
+ }[];
575
+ }) => void): void;
576
+ /** Stream task log chunk. */
577
+ emitTaskLog(sid: string, taskId: string, outputFile: string, chunk: string, offset: number): void;
578
+ /**
579
+ * Enable automation handling — agent will process webhook, supervisor,
580
+ * and task triggers from the server by spawning Happy CLI sessions.
581
+ */
582
+ enableAutomation(serverUrl: string, authToken: string, scheduler: AutomationScheduler): void;
583
+ /** Internal dispatch for ephemeral events that need automation handling. */
584
+ private handleAutomationEvent;
391
585
  /** Seed initial Tailscale info detected before connect. */
392
586
  setTailscaleInfo(info: TailscaleInfo): void;
393
587
  /** Attach a TunnelManager for periodic tunnel state refresh. */
@@ -399,5 +593,26 @@ declare class MachineClient {
399
593
  private stopTailscaleRefresh;
400
594
  }
401
595
 
402
- export { MachineClient, RpcHandlerManager, SessionClient, authLogin, authLogout, authStatus, createRpcHandlerManager, createSession, deleteSession, fetchMessagesAfterSeq, getOrCreateMachine, getSessionMessages, listActiveSessions, listMachines, listSessions, loadConfig, readCredentials, requireCredentials, resolveSessionEncryption, sendMessagesBatch };
403
- export type { Config, Credentials, DecryptedMessage, DecryptedSession, EncryptionVariant, MachineClientOptions, RpcHandler, RpcHandlerConfig, SessionClientOptions, SessionEncryption };
596
+ /**
597
+ * Daemon mode persistent background process that connects to the server,
598
+ * registers RPC handlers, and processes automation triggers.
599
+ *
600
+ * Usage: happy-agent daemon start [--directory <dir>]
601
+ *
602
+ * The daemon:
603
+ * 1. Loads credentials and config
604
+ * 2. Registers/gets machine identity
605
+ * 3. Detects Tailscale and tunnel state
606
+ * 4. Creates MachineClient with all RPC handlers
607
+ * 5. Enables automation (webhook/supervisor/task triggers)
608
+ * 6. Runs until SIGTERM/SIGINT
609
+ */
610
+ declare function startDaemon(options: {
611
+ directory?: string;
612
+ foreground?: boolean;
613
+ }): Promise<void>;
614
+ declare function stopDaemon(): void;
615
+ declare function daemonStatus(): void;
616
+
617
+ export { MachineClient, RpcHandlerManager, SessionClient, authLogin, authLogout, authStatus, createRpcHandlerManager, createSession, daemonStatus, deleteSession, fetchMessagesAfterSeq, getOrCreateMachine, getSessionMessages, listActiveSessions, listMachines, listSessions, loadConfig, readCredentials, requireCredentials, resolveSessionEncryption, sendMessagesBatch, startDaemon, stopDaemon };
618
+ export type { Config, Credentials, DecryptedMessage, DecryptedSession, EncryptionVariant, EphemeralEvent, MachineClientOptions, RpcHandler, RpcHandlerConfig, SessionClientOptions, SessionEncryption };