@hasna/loops 0.4.13 → 0.4.14

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +74 -3
  2. package/README.md +54 -12
  3. package/dist/api/index.d.ts +35 -0
  4. package/dist/api/index.js +695 -10
  5. package/dist/cli/index.js +1633 -366
  6. package/dist/cli/ui.d.ts +44 -0
  7. package/dist/daemon/index.js +948 -212
  8. package/dist/generated/storage-kit/health.d.ts +19 -0
  9. package/dist/generated/storage-kit/index.d.ts +7 -0
  10. package/dist/generated/storage-kit/migrations.d.ts +47 -0
  11. package/dist/generated/storage-kit/mode.d.ts +47 -0
  12. package/dist/generated/storage-kit/pool.d.ts +33 -0
  13. package/dist/generated/storage-kit/query.d.ts +35 -0
  14. package/dist/generated/storage-kit/tls.d.ts +25 -0
  15. package/dist/index.d.ts +2 -2
  16. package/dist/index.js +3567 -2010
  17. package/dist/lib/health.d.ts +83 -3
  18. package/dist/lib/mode.js +14 -2
  19. package/dist/lib/scheduler.d.ts +5 -2
  20. package/dist/lib/storage/index.d.ts +1 -0
  21. package/dist/lib/storage/index.js +1329 -211
  22. package/dist/lib/storage/pg-executor.d.ts +27 -0
  23. package/dist/lib/storage/pg-runner-claim.d.ts +40 -0
  24. package/dist/lib/storage/postgres-loop-storage.d.ts +114 -0
  25. package/dist/lib/storage/sqlite.js +336 -8
  26. package/dist/lib/store.d.ts +234 -0
  27. package/dist/lib/store.js +350 -8
  28. package/dist/mcp/index.js +1067 -306
  29. package/dist/runner/index.js +124 -25
  30. package/dist/sdk/http.d.ts +115 -0
  31. package/dist/sdk/http.js +145 -0
  32. package/dist/sdk/index.d.ts +5 -1
  33. package/dist/sdk/index.js +1052 -312
  34. package/dist/serve/index.d.ts +4 -0
  35. package/dist/serve/index.js +7619 -0
  36. package/dist/types.d.ts +3 -0
  37. package/docs/CUTOVER-RUNBOOK.md +61 -0
  38. package/docs/USAGE.md +50 -11
  39. package/package.json +14 -2
@@ -1,10 +1,13 @@
1
- import type { Loop, LoopRun } from "../types.js";
1
+ import type { Loop, LoopRun, LoopStatus } from "../types.js";
2
+ import type { DaemonStatus } from "../daemon/control.js";
3
+ import type { DoctorCheck, DoctorReport } from "./doctor.js";
2
4
  import type { Store } from "./store.js";
3
- export type RunFailureClassification = "rate_limit" | "auth" | "model_not_found" | "context_length" | "schema_response_format" | "node_init" | "preflight" | "route_functional" | "timeout" | "sigsegv" | "skipped_previous_active" | "circuit_breaker" | "unknown";
5
+ export type RunFailureClassification = "rate_limit" | "auth" | "provider_unavailable" | "model_not_found" | "context_length" | "schema_response_format" | "node_init" | "preflight" | "route_functional" | "timeout" | "sigsegv" | "restart_interrupted" | "skipped_previous_active" | "circuit_breaker" | "unknown";
4
6
  export interface RunFailureSignal {
5
7
  classification: RunFailureClassification;
6
8
  fingerprint: string;
7
9
  evidence: {
10
+ summary?: string;
8
11
  error?: string;
9
12
  stdout?: string;
10
13
  stderr?: string;
@@ -31,7 +34,7 @@ export interface RecommendedTaskUpsert {
31
34
  };
32
35
  }
33
36
  export interface LoopExpectationResult {
34
- loop: Pick<Loop, "id" | "name" | "status" | "nextRunAt">;
37
+ loop: Pick<Loop, "id" | "name" | "status" | "nextRunAt" | "retryScheduledFor">;
35
38
  ok: boolean;
36
39
  check: {
37
40
  id: "latest-run-succeeded" | "route-functional-health";
@@ -62,6 +65,81 @@ export interface LoopsHealthReport {
62
65
  classifications: Record<RunFailureClassification, number>;
63
66
  expectations: LoopExpectationResult[];
64
67
  }
68
+ export type HealthScanStatus = "ok" | "degraded" | "critical";
69
+ export type HealthScanFindingKind = "daemon" | "doctor" | "preflight" | "latest-run" | "stale-running";
70
+ export type HealthScanFindingSeverity = "critical" | "high" | "medium" | "low";
71
+ export interface HealthScanSelfHealAction {
72
+ kind: "daemon-start";
73
+ attempted: boolean;
74
+ ok?: boolean;
75
+ reason: string;
76
+ result?: Record<string, unknown>;
77
+ }
78
+ export interface HealthScanFinding {
79
+ kind: HealthScanFindingKind;
80
+ severity: HealthScanFindingSeverity;
81
+ fingerprint: string;
82
+ title: string;
83
+ message: string;
84
+ loop?: Pick<Loop, "id" | "name" | "status" | "nextRunAt"> & {
85
+ leaseMs?: number;
86
+ };
87
+ run?: LoopRun;
88
+ route?: LoopExpectationResult["route"];
89
+ ageMs?: number;
90
+ staleThresholdMs?: number;
91
+ classification?: RunFailureClassification;
92
+ doctorCheck?: DoctorCheck;
93
+ recommendedTask?: RecommendedTaskUpsert;
94
+ }
95
+ export interface LoopsHealthScan {
96
+ ok: boolean;
97
+ status: HealthScanStatus;
98
+ generatedAt: string;
99
+ includedStatuses: LoopStatus[];
100
+ counts: {
101
+ loops: number;
102
+ active: number;
103
+ paused: number;
104
+ stopped: number;
105
+ expired: number;
106
+ latestRunFindings: number;
107
+ staleRunning: number;
108
+ daemonFindings: number;
109
+ doctorFindings: number;
110
+ preflightFindings: number;
111
+ findings: number;
112
+ reportedFindings: number;
113
+ truncatedFindings: number;
114
+ };
115
+ daemon?: Pick<DaemonStatus, "running" | "stale" | "pid" | "host" | "loops" | "runs" | "logPath">;
116
+ doctor?: DoctorReport;
117
+ health: LoopsHealthReport;
118
+ selfHeals: HealthScanSelfHealAction[];
119
+ findings: HealthScanFinding[];
120
+ reports?: {
121
+ dir: string;
122
+ json: string;
123
+ markdown: string;
124
+ };
125
+ todos?: Record<string, unknown>;
126
+ }
127
+ export interface BuildHealthScanOptions {
128
+ includeStatuses?: LoopStatus[];
129
+ includeArchived?: boolean;
130
+ limit?: number;
131
+ latestRun?: boolean;
132
+ doctor?: DoctorReport;
133
+ daemon?: DaemonStatus;
134
+ selfHeals?: HealthScanSelfHealAction[];
135
+ maxFindings?: number;
136
+ staleRunningMs?: number;
137
+ now?: Date;
138
+ }
139
+ export interface WriteHealthScanReportsOptions {
140
+ reportDir?: string;
141
+ }
142
+ export declare const RESTART_INTERRUPTED_RUN_PREFIX = "daemon restart interrupted active run";
65
143
  export declare function classifyRunFailure(run: LoopRun): RunFailureSignal | undefined;
66
144
  export declare function expectationForLoop(store: Store, loop: Loop): LoopExpectationResult;
67
145
  export declare function buildHealthReport(store: Store, opts?: {
@@ -69,3 +147,5 @@ export declare function buildHealthReport(store: Store, opts?: {
69
147
  includeInactive?: boolean;
70
148
  limit?: number;
71
149
  }): LoopsHealthReport;
150
+ export declare function buildHealthScan(store: Store, opts?: BuildHealthScanOptions): LoopsHealthScan;
151
+ export declare function writeHealthScanReports(scan: LoopsHealthScan, opts?: WriteHealthScanReportsOptions): LoopsHealthScan;
package/dist/lib/mode.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // package.json
3
3
  var package_default = {
4
4
  name: "@hasna/loops",
5
- version: "0.4.13",
5
+ version: "0.4.14",
6
6
  description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
7
7
  type: "module",
8
8
  main: "dist/index.js",
@@ -11,6 +11,7 @@ var package_default = {
11
11
  loops: "dist/cli/index.js",
12
12
  "loops-daemon": "dist/daemon/index.js",
13
13
  "loops-api": "dist/api/index.js",
14
+ "loops-serve": "dist/serve/index.js",
14
15
  "loops-runner": "dist/runner/index.js",
15
16
  "loops-mcp": "dist/mcp/index.js"
16
17
  },
@@ -23,6 +24,14 @@ var package_default = {
23
24
  types: "./dist/sdk/index.d.ts",
24
25
  import: "./dist/sdk/index.js"
25
26
  },
27
+ "./sdk/http": {
28
+ types: "./dist/sdk/http.d.ts",
29
+ import: "./dist/sdk/http.js"
30
+ },
31
+ "./serve": {
32
+ types: "./dist/serve/index.d.ts",
33
+ import: "./dist/serve/index.js"
34
+ },
26
35
  "./mcp": {
27
36
  types: "./dist/mcp/index.d.ts",
28
37
  import: "./dist/mcp/index.js"
@@ -68,7 +77,7 @@ var package_default = {
68
77
  "LICENSE"
69
78
  ],
70
79
  scripts: {
71
- build: "rm -rf dist && bun build src/cli/index.ts src/daemon/index.ts src/api/index.ts src/runner/index.ts src/mcp/index.ts src/index.ts src/sdk/index.ts src/lib/store.ts src/lib/mode.ts src/lib/storage/index.ts src/lib/storage/contract.ts src/lib/storage/sqlite.ts src/lib/storage/postgres.ts src/lib/storage/postgres-schema.ts --root src --outdir dist --target bun --packages external && chmod +x dist/cli/index.js dist/daemon/index.js dist/api/index.js dist/runner/index.js dist/mcp/index.js && tsc -p tsconfig.build.json --emitDeclarationOnly --outDir dist",
80
+ build: "rm -rf dist && bun build src/cli/index.ts src/daemon/index.ts src/api/index.ts src/serve/index.ts src/runner/index.ts src/mcp/index.ts src/index.ts src/sdk/index.ts src/sdk/http.ts src/lib/store.ts src/lib/mode.ts src/lib/storage/index.ts src/lib/storage/contract.ts src/lib/storage/sqlite.ts src/lib/storage/postgres.ts src/lib/storage/postgres-schema.ts --root src --outdir dist --target bun --packages external && chmod +x dist/cli/index.js dist/daemon/index.js dist/api/index.js dist/serve/index.js dist/runner/index.js dist/mcp/index.js && tsc -p tsconfig.build.json --emitDeclarationOnly --outDir dist",
72
81
  "build:bin": "bun build src/cli/index.ts --compile --outfile dist/loops",
73
82
  typecheck: "tsc --noEmit",
74
83
  test: "bun test",
@@ -103,11 +112,13 @@ var package_default = {
103
112
  bun: ">=1.0.0"
104
113
  },
105
114
  dependencies: {
115
+ "@hasna/contracts": "^0.4.1",
106
116
  "@hasna/events": "^0.1.9",
107
117
  "@modelcontextprotocol/sdk": "^1.29.0",
108
118
  "@openrouter/ai-sdk-provider": "2.9.1",
109
119
  ai: "6.0.204",
110
120
  commander: "^13.1.0",
121
+ pg: "^8.13.1",
111
122
  zod: "4.4.3"
112
123
  },
113
124
  optionalDependencies: {
@@ -115,6 +126,7 @@ var package_default = {
115
126
  },
116
127
  devDependencies: {
117
128
  "@types/bun": "latest",
129
+ "@types/pg": "^8.11.10",
118
130
  typescript: "^5.7.3"
119
131
  },
120
132
  publishConfig: {
@@ -97,8 +97,8 @@ export declare const MAX_SKIPS_PER_LOOP_PER_TICK = 10;
97
97
  /**
98
98
  * Exponential retry backoff with jitter:
99
99
  * delay = retryDelayMs * 2^(attempt-1) * (0.5 + random), capped at 6h.
100
- * Failures classified as rate_limit/auth back off 4x harder, since hammering
101
- * a throttled or misconfigured provider only makes things worse.
100
+ * Failures classified as provider-gated back off 4x harder, since hammering
101
+ * a throttled, misconfigured, or unavailable provider only makes things worse.
102
102
  */
103
103
  export declare function retryBackoffDelayMs(loop: Loop, run: LoopRun, random?: () => number): number;
104
104
  export interface AdvanceLoopOptions {
@@ -129,6 +129,9 @@ export declare function executeClaimedRun(deps: {
129
129
  beforeFinalize?: (loop: Loop, run: LoopRun) => void;
130
130
  daemonLeaseId?: string;
131
131
  execute?: (loop: Loop, run: LoopRun) => Promise<ExecutorResult>;
132
+ finalizeResult?: (result: ExecutorResult, loop: Loop, run: LoopRun) => Omit<ExecutorResult, "status"> & {
133
+ status: LoopRun["status"];
134
+ };
132
135
  onError?: (loop: Loop, error: unknown) => void;
133
136
  }): Promise<LoopRun>;
134
137
  export declare function claimDueRuns(deps: SchedulerDeps & {
@@ -2,5 +2,6 @@ export type { AppliedStorageMigration, AuditEventRecord, LoopStorageBackend, Loo
2
2
  export { SqliteLoopStorage, createSqliteLoopStorage } from "./sqlite.js";
3
3
  export { PostgresStorage, createPostgresStorage } from "./postgres.js";
4
4
  export type { PostgresQueryExecutor } from "./postgres.js";
5
+ export { PostgresLoopStorage, createPostgresLoopStorage, NotImplementedError, } from "./postgres-loop-storage.js";
5
6
  export { POSTGRES_MIGRATION_LEDGER_TABLE, POSTGRES_STORAGE_MIGRATIONS, checksumStorageSql, } from "./postgres-schema.js";
6
7
  export { Store } from "../store.js";