@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.
- package/CHANGELOG.md +99 -3
- package/README.md +171 -39
- package/dist/api/index.d.ts +36 -0
- package/dist/api/index.js +1518 -15
- package/dist/cli/index.js +7280 -3213
- package/dist/cli/ui.d.ts +44 -0
- package/dist/daemon/index.js +1433 -303
- package/dist/generated/storage-kit/health.d.ts +19 -0
- package/dist/generated/storage-kit/index.d.ts +7 -0
- package/dist/generated/storage-kit/migrations.d.ts +47 -0
- package/dist/generated/storage-kit/mode.d.ts +47 -0
- package/dist/generated/storage-kit/pool.d.ts +33 -0
- package/dist/generated/storage-kit/query.d.ts +35 -0
- package/dist/generated/storage-kit/tls.d.ts +25 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +8689 -4837
- package/dist/lib/cloud/mode.d.ts +17 -0
- package/dist/lib/cloud/resolve.d.ts +16 -0
- package/dist/lib/cloud/storage.d.ts +82 -0
- package/dist/lib/cloud/transport.d.ts +148 -0
- package/dist/lib/format.d.ts +2 -1
- package/dist/lib/health.d.ts +83 -3
- package/dist/lib/migration.d.ts +40 -0
- package/dist/lib/mode.js +15 -3
- package/dist/lib/route/index.d.ts +2 -0
- package/dist/lib/route/policies.d.ts +51 -0
- package/dist/lib/route/provider-admission.d.ts +37 -0
- package/dist/lib/route/throttle.d.ts +4 -0
- package/dist/lib/route/types.d.ts +8 -0
- package/dist/lib/run-receipts.d.ts +14 -0
- package/dist/lib/scheduler.d.ts +5 -2
- package/dist/lib/storage/contract.d.ts +7 -1
- package/dist/lib/storage/index.d.ts +1 -0
- package/dist/lib/storage/index.js +2028 -231
- package/dist/lib/storage/pg-executor.d.ts +27 -0
- package/dist/lib/storage/pg-runner-claim.d.ts +40 -0
- package/dist/lib/storage/postgres-loop-storage.d.ts +120 -0
- package/dist/lib/storage/postgres-schema.js +29 -0
- package/dist/lib/storage/postgres.js +29 -0
- package/dist/lib/storage/sqlite.d.ts +6 -0
- package/dist/lib/storage/sqlite.js +748 -26
- package/dist/lib/store/index.d.ts +268 -0
- package/dist/lib/store.d.ts +282 -1
- package/dist/lib/store.js +746 -26
- package/dist/lib/template-kit.d.ts +25 -0
- package/dist/lib/templates.d.ts +16 -1
- package/dist/mcp/http.d.ts +16 -0
- package/dist/mcp/index.js +2885 -634
- package/dist/runner/index.js +198 -32
- package/dist/sdk/http.d.ts +182 -0
- package/dist/sdk/http.js +166 -0
- package/dist/sdk/index.d.ts +55 -18
- package/dist/sdk/index.js +2734 -617
- package/dist/serve/index.d.ts +4 -0
- package/dist/serve/index.js +9095 -0
- package/dist/types.d.ts +52 -0
- package/docs/AUTOMATION_RUNTIME_DESIGN.md +442 -1
- package/docs/CUTOVER-RUNBOOK.md +78 -0
- package/docs/DEPLOYMENT_MODES.md +35 -18
- package/docs/RUNTIME_BOUNDARY.md +203 -0
- package/docs/USAGE.md +195 -38
- package/package.json +15 -3
package/dist/sdk/http.js
ADDED
|
@@ -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
|
+
};
|
package/dist/sdk/index.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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;
|