@hasna/loops 0.4.13 → 0.4.22

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 (62) hide show
  1. package/CHANGELOG.md +99 -3
  2. package/README.md +171 -39
  3. package/dist/api/index.d.ts +36 -0
  4. package/dist/api/index.js +1518 -15
  5. package/dist/cli/index.js +7280 -3213
  6. package/dist/cli/ui.d.ts +44 -0
  7. package/dist/daemon/index.js +1433 -303
  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 +8689 -4837
  17. package/dist/lib/cloud/mode.d.ts +17 -0
  18. package/dist/lib/cloud/resolve.d.ts +16 -0
  19. package/dist/lib/cloud/storage.d.ts +82 -0
  20. package/dist/lib/cloud/transport.d.ts +148 -0
  21. package/dist/lib/format.d.ts +2 -1
  22. package/dist/lib/health.d.ts +83 -3
  23. package/dist/lib/migration.d.ts +40 -0
  24. package/dist/lib/mode.js +15 -3
  25. package/dist/lib/route/index.d.ts +2 -0
  26. package/dist/lib/route/policies.d.ts +51 -0
  27. package/dist/lib/route/provider-admission.d.ts +37 -0
  28. package/dist/lib/route/throttle.d.ts +4 -0
  29. package/dist/lib/route/types.d.ts +8 -0
  30. package/dist/lib/run-receipts.d.ts +14 -0
  31. package/dist/lib/scheduler.d.ts +5 -2
  32. package/dist/lib/storage/contract.d.ts +7 -1
  33. package/dist/lib/storage/index.d.ts +1 -0
  34. package/dist/lib/storage/index.js +2028 -231
  35. package/dist/lib/storage/pg-executor.d.ts +27 -0
  36. package/dist/lib/storage/pg-runner-claim.d.ts +40 -0
  37. package/dist/lib/storage/postgres-loop-storage.d.ts +120 -0
  38. package/dist/lib/storage/postgres-schema.js +29 -0
  39. package/dist/lib/storage/postgres.js +29 -0
  40. package/dist/lib/storage/sqlite.d.ts +6 -0
  41. package/dist/lib/storage/sqlite.js +748 -26
  42. package/dist/lib/store/index.d.ts +268 -0
  43. package/dist/lib/store.d.ts +282 -1
  44. package/dist/lib/store.js +746 -26
  45. package/dist/lib/template-kit.d.ts +25 -0
  46. package/dist/lib/templates.d.ts +16 -1
  47. package/dist/mcp/http.d.ts +16 -0
  48. package/dist/mcp/index.js +2885 -634
  49. package/dist/runner/index.js +198 -32
  50. package/dist/sdk/http.d.ts +182 -0
  51. package/dist/sdk/http.js +166 -0
  52. package/dist/sdk/index.d.ts +55 -18
  53. package/dist/sdk/index.js +2734 -617
  54. package/dist/serve/index.d.ts +4 -0
  55. package/dist/serve/index.js +9095 -0
  56. package/dist/types.d.ts +52 -0
  57. package/docs/AUTOMATION_RUNTIME_DESIGN.md +442 -1
  58. package/docs/CUTOVER-RUNBOOK.md +78 -0
  59. package/docs/DEPLOYMENT_MODES.md +35 -18
  60. package/docs/RUNTIME_BOUNDARY.md +203 -0
  61. package/docs/USAGE.md +195 -38
  62. package/package.json +15 -3
@@ -0,0 +1,166 @@
1
+ // @bun
2
+ // src/sdk/http.ts
3
+ class ApiError extends Error {
4
+ status;
5
+ body;
6
+ constructor(status, message, body) {
7
+ super(message);
8
+ this.status = status;
9
+ this.body = body;
10
+ this.name = "ApiError";
11
+ }
12
+ }
13
+
14
+ class LoopsClient {
15
+ baseUrl;
16
+ apiKey;
17
+ fetchImpl;
18
+ baseHeaders;
19
+ constructor(options) {
20
+ if (!options.baseUrl)
21
+ throw new Error("LoopsClient requires a baseUrl.");
22
+ this.baseUrl = options.baseUrl.replace(/\/$/, "");
23
+ this.apiKey = options.apiKey;
24
+ this.fetchImpl = options.fetch ?? globalThis.fetch;
25
+ this.baseHeaders = options.headers ?? {};
26
+ }
27
+ async request(method, path, opts) {
28
+ const url = new URL(this.baseUrl + path);
29
+ if (opts.query) {
30
+ for (const [key, value] of Object.entries(opts.query)) {
31
+ if (value !== undefined && value !== null)
32
+ url.searchParams.set(key, String(value));
33
+ }
34
+ }
35
+ const headers = { Accept: "application/json", ...this.baseHeaders, ...opts.init?.headers };
36
+ if (this.apiKey)
37
+ headers["x-api-key"] = this.apiKey;
38
+ let payload;
39
+ if (opts.body !== undefined) {
40
+ headers["Content-Type"] = "application/json";
41
+ payload = JSON.stringify(opts.body);
42
+ }
43
+ const response = await this.fetchImpl(url.toString(), { ...opts.init, method, headers, body: payload });
44
+ const text = await response.text();
45
+ const data = text ? (() => {
46
+ try {
47
+ return JSON.parse(text);
48
+ } catch {
49
+ return text;
50
+ }
51
+ })() : undefined;
52
+ if (!response.ok) {
53
+ throw new ApiError(response.status, `${method} ${path} failed: ${response.status}`, data);
54
+ }
55
+ return data;
56
+ }
57
+ async healthCheck(init) {
58
+ return this.request("GET", `/health`, {
59
+ body: undefined,
60
+ query: undefined,
61
+ init
62
+ });
63
+ }
64
+ async readyCheck(init) {
65
+ return this.request("GET", `/ready`, {
66
+ body: undefined,
67
+ query: undefined,
68
+ init
69
+ });
70
+ }
71
+ async listLoops(query, init) {
72
+ return this.request("GET", `/v1/loops`, {
73
+ body: undefined,
74
+ query,
75
+ init
76
+ });
77
+ }
78
+ async createLoop(body, init) {
79
+ return this.request("POST", `/v1/loops`, {
80
+ body,
81
+ query: undefined,
82
+ init
83
+ });
84
+ }
85
+ async getLoop(id, init) {
86
+ return this.request("GET", `/v1/loops/${encodeURIComponent(String(id))}`, {
87
+ body: undefined,
88
+ query: undefined,
89
+ init
90
+ });
91
+ }
92
+ async deleteLoop(id, init) {
93
+ return this.request("DELETE", `/v1/loops/${encodeURIComponent(String(id))}`, {
94
+ body: undefined,
95
+ query: undefined,
96
+ init
97
+ });
98
+ }
99
+ async updateLoop(id, body, init) {
100
+ return this.request("PATCH", `/v1/loops/${encodeURIComponent(String(id))}`, {
101
+ body,
102
+ query: undefined,
103
+ init
104
+ });
105
+ }
106
+ async archiveLoop(id, init) {
107
+ return this.request("POST", `/v1/loops/${encodeURIComponent(String(id))}/archive`, {
108
+ body: undefined,
109
+ query: undefined,
110
+ init
111
+ });
112
+ }
113
+ async unarchiveLoop(id, init) {
114
+ return this.request("POST", `/v1/loops/${encodeURIComponent(String(id))}/unarchive`, {
115
+ body: undefined,
116
+ query: undefined,
117
+ init
118
+ });
119
+ }
120
+ async listRunReceipts(query, init) {
121
+ return this.request("GET", `/v1/receipts`, {
122
+ body: undefined,
123
+ query,
124
+ init
125
+ });
126
+ }
127
+ async writeRunReceipt(body, init) {
128
+ return this.request("POST", `/v1/receipts`, {
129
+ body,
130
+ query: undefined,
131
+ init
132
+ });
133
+ }
134
+ async getRunReceipt(runId, init) {
135
+ return this.request("GET", `/v1/receipts/${encodeURIComponent(String(runId))}`, {
136
+ body: undefined,
137
+ query: undefined,
138
+ init
139
+ });
140
+ }
141
+ async listRuns(query, init) {
142
+ return this.request("GET", `/v1/runs`, {
143
+ body: undefined,
144
+ query,
145
+ init
146
+ });
147
+ }
148
+ async getRun(id, init) {
149
+ return this.request("GET", `/v1/runs/${encodeURIComponent(String(id))}`, {
150
+ body: undefined,
151
+ query: undefined,
152
+ init
153
+ });
154
+ }
155
+ async getVersion(init) {
156
+ return this.request("GET", `/version`, {
157
+ body: undefined,
158
+ query: undefined,
159
+ init
160
+ });
161
+ }
162
+ }
163
+ export {
164
+ LoopsClient,
165
+ ApiError
166
+ };
@@ -1,13 +1,22 @@
1
- import type { CreateLoopInput, Goal, GoalRun, Loop, LoopRun, LoopStatus, OpenAutomationsRuntimeBinding, RunStatus } from "../types.js";
1
+ import type { CreateLoopInput, Goal, GoalRun, Loop, LoopRun, LoopStatus, OpenAutomationsRuntimeBinding, RunReceipt, RunStatus, WriteRunReceiptInput } from "../types.js";
2
2
  import { type DoctorReport } from "../lib/doctor.js";
3
- import { type LoopsHealthReport } from "../lib/health.js";
3
+ import { type BuildHealthScanOptions, type LoopsHealthReport, type LoopsHealthScan } from "../lib/health.js";
4
4
  import { type ApplyLoopsMigrationResult, type ExportLoopsMigrationOptions, type ImportLoopsMigrationOptions, type LoopsMigrationBundle, type LoopsMigrationPlan, type SelfHostedPlanOptions } from "../lib/migration.js";
5
5
  import { tick } from "../lib/scheduler.js";
6
6
  import { Store } from "../lib/store.js";
7
+ import { type LoopStore } from "../lib/store/index.js";
7
8
  export { runGoal } from "../lib/goal/runner.js";
8
9
  export { LOOPS_MIGRATION_SCHEMA, applyImportMigrationBundle, buildImportMigrationPlan, buildSelfHostedMigrationPlan, exportLoopsMigrationBundle, migrationHash, registerSelfHostedRunner, validateLoopsMigrationBundle, } from "../lib/migration.js";
9
10
  export type { ApplyLoopsMigrationResult, ExportLoopsMigrationOptions, ImportLoopsMigrationOptions, LoopsMigrationAction, LoopsMigrationBundle, LoopsMigrationPlan, LoopsMigrationPlanRow, LoopsMigrationPlanSummary, LoopsMigrationResource, RunnerRegistrationOptions, RunnerRegistrationResult, SelfHostedPlanOptions, } from "../lib/migration.js";
10
11
  export interface LoopsClientOptions {
12
+ /**
13
+ * Inject an on-box sqlite {@link Store} (mainly for tests and in-process local
14
+ * runtimes). When provided, both data and local-runtime operations run against
15
+ * it. When omitted, the data store is resolved from the client-flip env via
16
+ * {@link getStore} — the local sqlite store OR the hosted `/v1` API when
17
+ * HASNA_LOOPS_API_URL/HASNA_LOOPS_API_KEY (or HASNA_LOOPS_STORAGE_MODE) select
18
+ * it — so every data method routes through the one Store abstraction.
19
+ */
11
20
  store?: Store;
12
21
  /**
13
22
  * Claim owner id for inline runs. Keep the `<surface>:<pid>` shape (see
@@ -29,31 +38,59 @@ export interface ListRunsFilters {
29
38
  status?: RunStatus;
30
39
  limit?: number;
31
40
  }
41
+ export interface ListRunReceiptsFilters {
42
+ loopId?: string;
43
+ repo?: string;
44
+ taskId?: string;
45
+ knowledgeId?: string;
46
+ status?: string;
47
+ limit?: number;
48
+ }
32
49
  export declare class LoopsClient {
33
- readonly store: Store;
50
+ /**
51
+ * The resolved data store: an on-box {@link LocalStore} (sqlite) or the hosted
52
+ * {@link ApiStore} (`/v1` + bearer key). EVERY data method routes through here,
53
+ * so nothing silently touches the on-box island while flipped to the cloud API.
54
+ */
55
+ readonly store: LoopStore;
34
56
  private readonly ownStore;
35
57
  private readonly runnerId;
36
58
  constructor(opts?: LoopsClientOptions);
37
- create(input: CreateLoopInput): Loop;
38
- list(filters?: ListLoopsFilters): Loop[];
39
- get(idOrName: string): Loop;
40
- pause(idOrName: string): Loop;
41
- resume(idOrName: string): Loop;
42
- stop(idOrName: string): Loop;
43
- archive(idOrName: string): Loop;
44
- unarchive(idOrName: string): Loop;
45
- delete(idOrName: string): boolean;
46
- runs(idOrName?: string, filters?: ListRunsFilters): LoopRun[];
59
+ /**
60
+ * The raw on-box sqlite {@link Store} for local-runtime operations that cannot
61
+ * route over HTTP — the scheduler (tick / inline run-now), migration
62
+ * export/import, and local diagnostics (doctor/health). These act on THIS
63
+ * machine's runtime and database, so they fail loudly instead of silently
64
+ * hitting the on-box island when the client is flipped to the hosted API.
65
+ */
66
+ private localRuntime;
67
+ create(input: CreateLoopInput): Promise<Loop>;
68
+ list(filters?: ListLoopsFilters): Promise<Loop[]>;
69
+ get(idOrName: string): Promise<Loop>;
70
+ pause(idOrName: string): Promise<Loop>;
71
+ resume(idOrName: string): Promise<Loop>;
72
+ stop(idOrName: string): Promise<Loop>;
73
+ archive(idOrName: string): Promise<Loop>;
74
+ unarchive(idOrName: string): Promise<Loop>;
75
+ delete(idOrName: string): Promise<boolean>;
76
+ runs(idOrName?: string, filters?: ListRunsFilters): Promise<LoopRun[]>;
77
+ writeReceipt(input: WriteRunReceiptInput): Promise<RunReceipt>;
78
+ receipt(runId: string): Promise<RunReceipt | undefined>;
79
+ receipts(filters?: ListRunReceiptsFilters): Promise<RunReceipt[]>;
80
+ goal(idOrName: string): Promise<{
81
+ goal?: Goal;
82
+ runs: GoalRun[];
83
+ }>;
47
84
  doctor(): DoctorReport;
48
85
  health(opts?: {
49
86
  includeArchived?: boolean;
50
87
  includeInactive?: boolean;
51
88
  limit?: number;
52
89
  }): LoopsHealthReport;
53
- goal(idOrName: string): {
54
- goal?: Goal;
55
- runs: GoalRun[];
56
- };
90
+ healthScan(opts?: Omit<BuildHealthScanOptions, "doctor" | "daemon" | "selfHeals"> & {
91
+ doctor?: boolean;
92
+ daemon?: boolean;
93
+ }): LoopsHealthScan;
57
94
  tick(): Promise<Awaited<ReturnType<typeof tick>>>;
58
95
  runNow(idOrName: string): Promise<LoopRun>;
59
96
  exportBundle(opts?: ExportLoopsMigrationOptions): LoopsMigrationBundle;
@@ -62,7 +99,7 @@ export declare class LoopsClient {
62
99
  planSelfHostedMigration(opts?: Omit<SelfHostedPlanOptions, "operation"> & {
63
100
  operation?: SelfHostedPlanOptions["operation"];
64
101
  }): Promise<LoopsMigrationPlan>;
65
- close(): void;
102
+ close(): Promise<void>;
66
103
  }
67
104
  export declare function loops(opts?: LoopsClientOptions): LoopsClient;
68
105
  export declare function openAutomationsRuntimeBinding(overrides?: Partial<OpenAutomationsRuntimeBinding>): OpenAutomationsRuntimeBinding;