@hasna/loops 0.4.27 → 0.4.29
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 +205 -1
- package/README.md +232 -49
- package/dist/api/index.d.ts +20 -19
- package/dist/api/index.js +7770 -1342
- package/dist/cli/index.js +2976 -966
- package/dist/cli/safe-error-context.d.ts +1 -0
- package/dist/daemon/daemon.d.ts +1 -0
- package/dist/daemon/index.js +1899 -499
- package/dist/generated/storage-kit/index.d.ts +1 -1
- package/dist/generated/storage-kit/mode.d.ts +6 -12
- package/dist/index.d.ts +3 -3
- package/dist/index.js +5319 -1096
- package/dist/lib/advancement.d.ts +52 -0
- package/dist/lib/agent-adapter.d.ts +20 -2
- package/dist/lib/auth/route-policy.d.ts +14 -0
- package/dist/lib/auth/tenant-auth.d.ts +38 -0
- package/dist/lib/cloud/mode.d.ts +2 -8
- package/dist/lib/cloud/storage.d.ts +1 -2
- package/dist/lib/cloud/transport.d.ts +4 -18
- package/dist/lib/errors.d.ts +43 -1
- package/dist/lib/executor.d.ts +9 -0
- package/dist/lib/format.d.ts +2 -2
- package/dist/lib/goal/runner.d.ts +36 -3
- package/dist/lib/health.d.ts +1 -1
- package/dist/lib/labels.d.ts +4 -0
- package/dist/lib/loop-status.d.ts +4 -0
- package/dist/lib/migration.d.ts +70 -17
- package/dist/lib/mode.d.ts +2 -5
- package/dist/lib/mode.js +28 -37
- package/dist/lib/route/fields.d.ts +8 -0
- package/dist/lib/route/index.d.ts +2 -2
- package/dist/lib/route/throttle.d.ts +7 -0
- package/dist/lib/route/types.d.ts +3 -0
- package/dist/lib/run-completion.d.ts +18 -0
- package/dist/lib/scheduler.d.ts +5 -11
- package/dist/lib/storage/contract.d.ts +15 -1
- package/dist/lib/storage/index.d.ts +1 -1
- package/dist/lib/storage/index.js +4083 -486
- package/dist/lib/storage/pg-executor.d.ts +10 -5
- package/dist/lib/storage/postgres-loop-storage.d.ts +52 -26
- package/dist/lib/storage/postgres-schema.d.ts +8 -0
- package/dist/lib/storage/postgres-schema.js +1326 -1
- package/dist/lib/storage/postgres.d.ts +3 -1
- package/dist/lib/storage/postgres.js +1356 -27
- package/dist/lib/storage/provider-credentials.d.ts +84 -0
- package/dist/lib/storage/shared-database-transfer.d.ts +136 -0
- package/dist/lib/storage/sqlite.d.ts +15 -2
- package/dist/lib/storage/sqlite.js +1379 -217
- package/dist/lib/storage/tenant-backfill-s3.d.ts +53 -0
- package/dist/lib/storage/tenant-backfill.d.ts +40 -0
- package/dist/lib/store/index.d.ts +14 -8
- package/dist/lib/store.d.ts +129 -7
- package/dist/lib/store.js +1352 -218
- package/dist/lib/templates.d.ts +11 -0
- package/dist/lib/workflow-events.d.ts +6 -0
- package/dist/lib/workflow-provenance.d.ts +8 -0
- package/dist/lib/workflow-runner.d.ts +52 -4
- package/dist/mcp/index.js +2074 -657
- package/dist/runner/index.d.ts +20 -2
- package/dist/runner/index.js +2146 -183
- package/dist/sdk/http.d.ts +364 -4
- package/dist/sdk/http.js +379 -1
- package/dist/sdk/index.d.ts +7 -2
- package/dist/sdk/index.js +2341 -742
- package/dist/serve/index.d.ts +14 -0
- package/dist/serve/index.js +13269 -1830
- package/dist/types.d.ts +75 -3
- package/docs/AUTOMATION_RUNTIME_DESIGN.md +32 -32
- package/docs/CUTOVER-RUNBOOK.md +158 -31
- package/docs/DEPLOYMENT_MODES.md +84 -56
- package/docs/RUNTIME_BOUNDARY.md +26 -26
- package/docs/SHARED-DATABASE-TRANSFER.md +95 -0
- package/docs/SHARED_KIT_EXTRACTION_INVENTORY.md +631 -0
- package/docs/TRANSCRIPT_LOOP_PATTERNS.md +3 -3
- package/docs/UNIFIED_PRODUCT_CONTRACT.md +365 -0
- package/docs/USAGE.md +150 -56
- package/docs/workflows/transcript-feedback-to-loops.json +2 -2
- package/package.json +8 -4
- package/dist/lib/storage/pg-runner-claim.d.ts +0 -40
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.
|
|
5
|
+
version: "0.4.29",
|
|
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",
|
|
@@ -81,11 +81,14 @@ var package_default = {
|
|
|
81
81
|
"build:bin": "bun build src/cli/index.ts --compile --outfile dist/loops",
|
|
82
82
|
typecheck: "tsc --noEmit",
|
|
83
83
|
test: "bun test",
|
|
84
|
+
"check:branding": "bun test scripts/check-branding.test.mjs && bun run scripts/check-branding.mjs",
|
|
84
85
|
"test:boundary": "bun run scripts/no-private-cloud-boundary.mjs",
|
|
86
|
+
"check:contracts": "bun run scripts/check-storage-kit.mjs && bun run scripts/check-contract-conformance.mjs",
|
|
87
|
+
"check:supply-chain": "bun audit && bun audit --production && bun run check:contracts && bun run check:branding && bun pm pack --dry-run --ignore-scripts && bun run test:boundary && bun run scripts/check-packed-boundary.mjs",
|
|
85
88
|
prepare: "test -d dist || bun run build",
|
|
86
89
|
"dev:cli": "bun run src/cli/index.ts",
|
|
87
90
|
"dev:daemon": "bun run src/daemon/index.ts",
|
|
88
|
-
prepublishOnly: "bun run build && bun run typecheck && bun test && bun run test:boundary"
|
|
91
|
+
prepublishOnly: "bun run build && bun run typecheck && bun test && bun run test:boundary && bun run check:supply-chain"
|
|
89
92
|
},
|
|
90
93
|
keywords: [
|
|
91
94
|
"loops",
|
|
@@ -112,6 +115,8 @@ var package_default = {
|
|
|
112
115
|
bun: ">=1.0.0"
|
|
113
116
|
},
|
|
114
117
|
dependencies: {
|
|
118
|
+
"@aws-sdk/client-secrets-manager": "^3.1083.0",
|
|
119
|
+
"@hasna/contracts": "0.5.2",
|
|
115
120
|
"@hasna/events": "^0.1.9",
|
|
116
121
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
117
122
|
"@openrouter/ai-sdk-provider": "2.9.1",
|
|
@@ -121,8 +126,7 @@ var package_default = {
|
|
|
121
126
|
zod: "4.4.3"
|
|
122
127
|
},
|
|
123
128
|
optionalDependencies: {
|
|
124
|
-
"@hasna/machines": "0.0.49"
|
|
125
|
-
"@hasna/contracts": "^0.4.2"
|
|
129
|
+
"@hasna/machines": "0.0.49"
|
|
126
130
|
},
|
|
127
131
|
devDependencies: {
|
|
128
132
|
"@types/bun": "latest",
|
|
@@ -144,13 +148,10 @@ function packageVersion() {
|
|
|
144
148
|
|
|
145
149
|
// src/lib/mode.ts
|
|
146
150
|
var LOOP_DEPLOYMENT_MODES = ["local", "self_hosted", "cloud"];
|
|
147
|
-
var MODE_ENV_KEYS = ["
|
|
148
|
-
var API_URL_ENV_KEYS = ["
|
|
149
|
-
var
|
|
150
|
-
var
|
|
151
|
-
var API_TOKEN_ENV_KEYS = ["LOOPS_API_TOKEN", "HASNA_LOOPS_API_TOKEN"];
|
|
152
|
-
var CLOUD_TOKEN_ENV_KEYS = ["LOOPS_CLOUD_TOKEN", "HASNA_LOOPS_CLOUD_TOKEN"];
|
|
153
|
-
var TOKEN_ENV_KEYS = [...API_TOKEN_ENV_KEYS, ...CLOUD_TOKEN_ENV_KEYS];
|
|
151
|
+
var MODE_ENV_KEYS = ["HASNA_LOOPS_STORAGE_MODE"];
|
|
152
|
+
var API_URL_ENV_KEYS = ["HASNA_LOOPS_API_URL"];
|
|
153
|
+
var DATABASE_URL_ENV_KEYS = ["HASNA_LOOPS_DATABASE_URL"];
|
|
154
|
+
var API_KEY_ENV_KEYS = ["HASNA_LOOPS_API_KEY"];
|
|
154
155
|
function envValue(env, keys) {
|
|
155
156
|
for (const key of keys) {
|
|
156
157
|
const value = env[key]?.trim();
|
|
@@ -160,14 +161,14 @@ function envValue(env, keys) {
|
|
|
160
161
|
return;
|
|
161
162
|
}
|
|
162
163
|
function normalizeLoopDeploymentMode(value) {
|
|
163
|
-
const normalized = value.trim()
|
|
164
|
+
const normalized = value.trim();
|
|
164
165
|
if (normalized === "local")
|
|
165
166
|
return "local";
|
|
166
|
-
if (normalized === "self_hosted"
|
|
167
|
+
if (normalized === "self_hosted")
|
|
167
168
|
return "self_hosted";
|
|
168
|
-
if (normalized === "cloud"
|
|
169
|
+
if (normalized === "cloud")
|
|
169
170
|
return "cloud";
|
|
170
|
-
throw new Error(`unsupported
|
|
171
|
+
throw new Error(`unsupported Loops deployment mode "${value}"; expected local, self_hosted, or cloud`);
|
|
171
172
|
}
|
|
172
173
|
function resolveLoopDeploymentMode(env = process.env) {
|
|
173
174
|
const explicitMode = envValue(env, MODE_ENV_KEYS);
|
|
@@ -177,9 +178,6 @@ function resolveLoopDeploymentMode(env = process.env) {
|
|
|
177
178
|
source: explicitMode.key
|
|
178
179
|
};
|
|
179
180
|
}
|
|
180
|
-
const cloudApiUrl = envValue(env, CLOUD_API_URL_ENV_KEYS);
|
|
181
|
-
if (cloudApiUrl)
|
|
182
|
-
return { deploymentMode: "cloud", source: cloudApiUrl.key };
|
|
183
181
|
const apiUrl = envValue(env, API_URL_ENV_KEYS);
|
|
184
182
|
if (apiUrl)
|
|
185
183
|
return { deploymentMode: "self_hosted", source: apiUrl.key };
|
|
@@ -191,11 +189,8 @@ function resolveLoopDeploymentMode(env = process.env) {
|
|
|
191
189
|
function loopControlPlaneConfig(env = process.env) {
|
|
192
190
|
return {
|
|
193
191
|
apiUrl: envValue(env, API_URL_ENV_KEYS)?.value,
|
|
194
|
-
cloudApiUrl: envValue(env, CLOUD_API_URL_ENV_KEYS)?.value,
|
|
195
192
|
databaseUrlPresent: Boolean(envValue(env, DATABASE_URL_ENV_KEYS)),
|
|
196
|
-
|
|
197
|
-
cloudAuthTokenPresent: Boolean(envValue(env, CLOUD_TOKEN_ENV_KEYS)),
|
|
198
|
-
authTokenPresent: Boolean(envValue(env, TOKEN_ENV_KEYS))
|
|
193
|
+
apiKeyPresent: Boolean(envValue(env, API_KEY_ENV_KEYS))
|
|
199
194
|
};
|
|
200
195
|
}
|
|
201
196
|
function sourceOfTruthForMode(mode) {
|
|
@@ -217,7 +212,7 @@ function remoteSchedulerBackendForMode(mode, config) {
|
|
|
217
212
|
if (mode === "local")
|
|
218
213
|
return "none";
|
|
219
214
|
if (mode === "cloud")
|
|
220
|
-
return config.
|
|
215
|
+
return config.apiUrl ? "hosted_control_plane_contract" : "unconfigured";
|
|
221
216
|
if (config.databaseUrlPresent)
|
|
222
217
|
return "postgres_contract";
|
|
223
218
|
if (config.apiUrl)
|
|
@@ -265,26 +260,22 @@ function buildDeploymentStatus(opts = {}) {
|
|
|
265
260
|
const active = resolveLoopDeploymentMode(env);
|
|
266
261
|
const deploymentMode = opts.perspective ?? active.deploymentMode;
|
|
267
262
|
const config = loopControlPlaneConfig(env);
|
|
268
|
-
const
|
|
269
|
-
const apiUrl = displayControlPlaneUrl(rawApiUrl);
|
|
263
|
+
const apiUrl = displayControlPlaneUrl(config.apiUrl);
|
|
270
264
|
const controlPlaneKind = deploymentMode === "local" ? "none" : deploymentMode;
|
|
271
|
-
const deploymentAuthTokenPresent = deploymentMode === "cloud" ? config.
|
|
272
|
-
const controlPlaneConfigured = deploymentMode === "local" ? false : deploymentMode === "self_hosted" ? Boolean(config.apiUrl || config.databaseUrlPresent) : Boolean(config.
|
|
265
|
+
const deploymentAuthTokenPresent = deploymentMode === "cloud" ? config.apiKeyPresent : deploymentMode === "self_hosted" ? config.apiKeyPresent : false;
|
|
266
|
+
const controlPlaneConfigured = deploymentMode === "local" ? false : deploymentMode === "self_hosted" ? Boolean(config.apiUrl || config.databaseUrlPresent) : Boolean(config.apiUrl && deploymentAuthTokenPresent);
|
|
273
267
|
const warnings = [];
|
|
274
268
|
if (deploymentMode === "self_hosted" && !controlPlaneConfigured) {
|
|
275
|
-
warnings.push("self_hosted mode needs
|
|
269
|
+
warnings.push("self_hosted mode needs HASNA_LOOPS_API_URL or HASNA_LOOPS_DATABASE_URL before it can become authoritative");
|
|
276
270
|
}
|
|
277
271
|
if (deploymentMode === "self_hosted" && config.databaseUrlPresent && !config.apiUrl) {
|
|
278
|
-
warnings.push("
|
|
272
|
+
warnings.push("HASNA_LOOPS_DATABASE_URL selects the self_hosted storage contract; loops-runner still needs HASNA_LOOPS_API_URL to claim remote work");
|
|
279
273
|
}
|
|
280
|
-
if (deploymentMode === "cloud" && config.apiUrl
|
|
281
|
-
warnings.push("
|
|
274
|
+
if (deploymentMode === "cloud" && !config.apiUrl) {
|
|
275
|
+
warnings.push("cloud mode needs HASNA_LOOPS_API_URL before it can become ready");
|
|
282
276
|
}
|
|
283
|
-
if (deploymentMode === "cloud" &&
|
|
284
|
-
warnings.push("cloud mode
|
|
285
|
-
}
|
|
286
|
-
if (deploymentMode === "cloud" && config.cloudApiUrl && !deploymentAuthTokenPresent) {
|
|
287
|
-
warnings.push("cloud mode needs LOOPS_CLOUD_TOKEN or HASNA_LOOPS_CLOUD_TOKEN before it can become ready");
|
|
277
|
+
if (deploymentMode === "cloud" && config.apiUrl && !deploymentAuthTokenPresent) {
|
|
278
|
+
warnings.push("cloud mode needs HASNA_LOOPS_API_KEY before it can become ready");
|
|
288
279
|
}
|
|
289
280
|
if (opts.perspective && opts.perspective !== active.deploymentMode) {
|
|
290
281
|
warnings.push(`active deployment mode is ${active.deploymentMode}; this is the ${opts.perspective} perspective`);
|
|
@@ -304,7 +295,7 @@ function buildDeploymentStatus(opts = {}) {
|
|
|
304
295
|
configured: controlPlaneConfigured,
|
|
305
296
|
apiUrl,
|
|
306
297
|
databaseUrlPresent: config.databaseUrlPresent,
|
|
307
|
-
|
|
298
|
+
apiKeyPresent: deploymentAuthTokenPresent
|
|
308
299
|
},
|
|
309
300
|
runner: {
|
|
310
301
|
required: deploymentMode !== "local",
|
|
@@ -15,6 +15,14 @@ export declare function firstTruthyField(records: Record<string, unknown>[], key
|
|
|
15
15
|
export declare function automationRecords(data: Record<string, unknown>, metadata: Record<string, unknown>): Record<string, unknown>[];
|
|
16
16
|
export declare function tagsFromValue(value: unknown): string[];
|
|
17
17
|
export declare function taskEventTags(records: Record<string, unknown>[]): string[];
|
|
18
|
+
/**
|
|
19
|
+
* First route-disallowed tag on a task, if any. Exposed so drains can exclude
|
|
20
|
+
* tasks that can never route (no-auto/blocked/manual/…) from the bounded
|
|
21
|
+
* candidate window *before* slicing, instead of letting them occupy scan slots
|
|
22
|
+
* every tick only to be rejected by eligibility — the "skip re-burns the window
|
|
23
|
+
* forever" half of the merge-lane decay.
|
|
24
|
+
*/
|
|
25
|
+
export declare function taskRouteDisallowedTag(tags: string[]): string | undefined;
|
|
18
26
|
export declare function taskRouteEligibility(data: Record<string, unknown>, metadata: Record<string, unknown>): {
|
|
19
27
|
eligible: boolean;
|
|
20
28
|
reason?: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Route engine: turns Hasna events, todos ready queues, and health/hygiene
|
|
3
|
-
* findings into deduped, throttled
|
|
3
|
+
* findings into deduped, throttled Loops workflow loops and todos tasks.
|
|
4
4
|
* Extracted from the CLI so commands stay thin and the SDK/daemon can reuse it.
|
|
5
5
|
*/
|
|
6
6
|
export * from "./types.js";
|
|
@@ -11,7 +11,7 @@ export { GateError, normalizeLoopTargetForStorage, normalizeWorkflowForStorage,
|
|
|
11
11
|
export { accountPoolFromOpts, normalizeAgentProvider, permissionModeFromOpts, providerAuthProfileFromOpts, providerRoutingPublic, resolveProviderRouting, roleAccountFromOpts, sandboxFromOpts, SUPPORTED_AGENT_PROVIDERS, type ProviderRoutingDecision, } from "./provider.js";
|
|
12
12
|
export { checkProviderAdmission, parseCodewithAdmissionDiagnostics, providerActiveCapFromOpts, providerAdmissionPlanFromOpts, type CodewithAdmissionDiagnostics, type ProviderAdmissionDecision, type ProviderAdmissionPlan, } from "./provider-admission.js";
|
|
13
13
|
export { prReviewRoutingDecision, type PrReviewRoutingDecision } from "./pr-review.js";
|
|
14
|
-
export { hasThrottleLimits, normalizeRoutePath, routeThrottleDecision, routeThrottleLimitsFromOpts, type RouteThrottleDecision, type RouteThrottleLimits, } from "./throttle.js";
|
|
14
|
+
export { hasThrottleLimits, normalizeRoutePath, routeThrottleDecision, routeThrottleLimitsFromInputs, routeThrottleLimitsFromOpts, type RouteThrottleDecision, type RouteThrottleLimits, } from "./throttle.js";
|
|
15
15
|
export { generatedRouteSandboxPreflight, readEventEnvelopeInput, routeEventByKind, routeGenericEvent, routeTodosTaskEvent, todosTaskRouteTemplateId, } from "./route-event.js";
|
|
16
16
|
export { drainTodosTaskRoutes, type DrainResult } from "./drain.js";
|
|
17
17
|
export { buildHygieneRouteTasks, parseHygieneChecks, taskAutoRoute, upsertRouteTasks, type HygieneCheckKind, type HygieneRouteTask, type RouteTaskSpec, type UpsertRouteTasksOptions, } from "./route-tasks.js";
|
|
@@ -26,6 +26,13 @@ export declare function routeThrottleLimitsFromOpts(opts: {
|
|
|
26
26
|
maxActivePerProjectGroup?: string;
|
|
27
27
|
maxPerProfile?: string;
|
|
28
28
|
}): RouteThrottleLimits;
|
|
29
|
+
export declare function routeThrottleLimitsFromInputs(opts: {
|
|
30
|
+
maxActive?: string;
|
|
31
|
+
maxActiveScope?: string;
|
|
32
|
+
maxActivePerProject?: string;
|
|
33
|
+
maxActivePerProjectGroup?: string;
|
|
34
|
+
maxPerProfile?: string;
|
|
35
|
+
}, data: Record<string, unknown>, metadata: Record<string, unknown>): RouteThrottleLimits;
|
|
29
36
|
export declare function hasThrottleLimits(limits: RouteThrottleLimits): boolean;
|
|
30
37
|
export declare function normalizeRoutePath(value: string | undefined): string | undefined;
|
|
31
38
|
export declare function routeProjectGroup(optsGroup: string | undefined, data: Record<string, unknown>, metadata: Record<string, unknown>): string | undefined;
|
|
@@ -29,6 +29,9 @@ export interface TodosTaskRouteOptions {
|
|
|
29
29
|
verifierIdleTimeout?: string;
|
|
30
30
|
permissionMode?: string;
|
|
31
31
|
sandbox?: string;
|
|
32
|
+
allowTool?: string[];
|
|
33
|
+
allowCommand?: string[];
|
|
34
|
+
safetyReason?: string;
|
|
32
35
|
manualBreakGlass?: boolean;
|
|
33
36
|
projectPath?: string;
|
|
34
37
|
projectGroup?: string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface RunCompletionInput {
|
|
2
|
+
startedAt: string;
|
|
3
|
+
requestedFinishedAt?: string;
|
|
4
|
+
requestedDurationMs?: number;
|
|
5
|
+
serverNow: Date;
|
|
6
|
+
}
|
|
7
|
+
export interface NormalizedRunCompletion {
|
|
8
|
+
finishedAt: string;
|
|
9
|
+
durationMs: number | undefined;
|
|
10
|
+
updatedAt: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Treat the control-plane clock as authoritative while retaining a bounded
|
|
14
|
+
* runner timestamp as evidence. In the normal case, where startedAt is not
|
|
15
|
+
* ahead of the server clock, finishedAt is clamped into [startedAt, serverNow].
|
|
16
|
+
* A backwards server-clock jump fails safe at serverNow and yields duration 0.
|
|
17
|
+
*/
|
|
18
|
+
export declare function normalizeRunCompletion(input: RunCompletionInput): NormalizedRunCompletion;
|
package/dist/lib/scheduler.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ExecutorResult, Loop, LoopRun } from "../types.js";
|
|
2
|
+
import { type CircuitBreakerThreshold } from "./advancement.js";
|
|
2
3
|
import type { Store } from "./store.js";
|
|
4
|
+
export { CIRCUIT_BREAKER_REASON_PREFIX, DEFAULT_CIRCUIT_BREAKER_THRESHOLD, MAX_RETRY_DELAY_MS, retryBackoffDelayMs, } from "./advancement.js";
|
|
3
5
|
export interface SchedulerDeps {
|
|
4
6
|
store: Store;
|
|
5
7
|
runnerId: string;
|
|
@@ -25,6 +27,7 @@ export interface TickResult {
|
|
|
25
27
|
export interface ClaimedLoopRun {
|
|
26
28
|
loop: Loop;
|
|
27
29
|
run: LoopRun;
|
|
30
|
+
claimToken: string;
|
|
28
31
|
}
|
|
29
32
|
export interface ClaimDueRunsResult extends TickResult {
|
|
30
33
|
claims: ClaimedLoopRun[];
|
|
@@ -90,21 +93,11 @@ export declare function runLoopNow(deps: RunLoopNowDeps & {
|
|
|
90
93
|
export declare function runLoopNow(deps: RunLoopNowDeps & {
|
|
91
94
|
mode?: "inline";
|
|
92
95
|
}): Promise<RunLoopNowExecuted>;
|
|
93
|
-
export declare const MAX_RETRY_DELAY_MS: number;
|
|
94
|
-
export declare const DEFAULT_CIRCUIT_BREAKER_THRESHOLD = 5;
|
|
95
|
-
export declare const CIRCUIT_BREAKER_REASON_PREFIX = "circuit breaker open";
|
|
96
96
|
export declare const MAX_SKIPS_PER_LOOP_PER_TICK = 10;
|
|
97
|
-
/**
|
|
98
|
-
* Exponential retry backoff with jitter:
|
|
99
|
-
* delay = retryDelayMs * 2^(attempt-1) * (0.5 + random), capped at 6h.
|
|
100
|
-
* Failures classified as provider-gated back off 4x harder, since hammering
|
|
101
|
-
* a throttled, misconfigured, or unavailable provider only makes things worse.
|
|
102
|
-
*/
|
|
103
|
-
export declare function retryBackoffDelayMs(loop: Loop, run: LoopRun, random?: () => number): number;
|
|
104
97
|
export interface AdvanceLoopOptions {
|
|
105
98
|
daemonLeaseId?: string;
|
|
106
99
|
random?: () => number;
|
|
107
|
-
circuitBreakerThreshold?:
|
|
100
|
+
circuitBreakerThreshold?: CircuitBreakerThreshold;
|
|
108
101
|
onRun?: (run: LoopRun) => void;
|
|
109
102
|
}
|
|
110
103
|
/**
|
|
@@ -123,6 +116,7 @@ export declare function advanceLoop(store: Store, loop: Loop, run: LoopRun, fini
|
|
|
123
116
|
export declare function executeClaimedRun(deps: {
|
|
124
117
|
store: Store;
|
|
125
118
|
runnerId: string;
|
|
119
|
+
claimToken: string;
|
|
126
120
|
loop: Loop;
|
|
127
121
|
run: LoopRun;
|
|
128
122
|
now?: () => Date;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Store } from "../store.js";
|
|
2
2
|
export type LoopStorageBackend = "sqlite" | "postgres";
|
|
3
|
-
export type LoopStorageMethodName = "createLoop" | "getLoop" | "findLoopByName" | "requireLoop" | "listLoops" | "dueLoops" | "updateLoop" | "renameLoop" | "archiveLoop" | "unarchiveLoop" | "deleteLoop" | "upsertMigrationLoop" | "upsertMigrationRun" | "upsertMigrationWorkflow" | "createWorkflow" | "getWorkflow" | "listWorkflows" | "countWorkflows" | "archiveWorkflow" | "createWorkflowInvocation" | "getWorkflowInvocation" | "listWorkflowInvocations" | "upsertWorkflowWorkItem" | "getWorkflowWorkItem" | "listWorkflowWorkItems" | "countActiveWorkflowWorkItems" | "admitWorkflowWorkItem" | "createGoal" | "getGoal" | "listGoals" | "createGoalPlanNodes" | "listGoalPlanNodes" | "updateGoalStatus" | "updateGoalPlanNode" | "recordGoalEvent" | "listGoalRuns" | "createWorkflowRun" | "getWorkflowRun" | "listWorkflowRuns" | "listWorkflowStepRuns" | "getWorkflowStepRun" | "startWorkflowStepRun" | "recoverWorkflowRun" | "finalizeWorkflowStepRun" | "finalizeWorkflowRun" | "appendWorkflowEvent" | "listWorkflowEvents" | "recordRunProcess" | "createSkippedRun" | "getRun" | "getRunBySlot" | "claimRun" | "finalizeRun" | "heartbeatRunLease" | "listRuns" | "writeRunReceipt" | "getRunReceipt" | "listRunReceipts" | "recoverExpiredRunLeases" | "recoverExpiredRunLeasesDetailed" | "countLoops" | "countRuns" | "pruneHistory" | "acquireDaemonLease" | "heartbeatDaemonLease" | "releaseDaemonLease" | "getDaemonLease";
|
|
3
|
+
export type LoopStorageMethodName = "createLoop" | "getLoop" | "findLoopByName" | "requireLoop" | "requireUniqueLoop" | "listLoops" | "dueLoops" | "updateLoop" | "advanceLoopIfCurrent" | "tripCircuitBreakerIfCurrent" | "renameLoop" | "archiveLoop" | "unarchiveLoop" | "deleteLoop" | "upsertMigrationLoop" | "upsertMigrationRun" | "upsertMigrationWorkflow" | "createWorkflow" | "getWorkflow" | "requireWorkflow" | "listWorkflows" | "countWorkflows" | "archiveWorkflow" | "createWorkflowInvocation" | "getWorkflowInvocation" | "listWorkflowInvocations" | "upsertWorkflowWorkItem" | "getWorkflowWorkItem" | "listWorkflowWorkItems" | "countActiveWorkflowWorkItems" | "admitWorkflowWorkItem" | "createGoal" | "getGoal" | "requireGoal" | "findGoalByContext" | "listGoals" | "createGoalPlanNodes" | "listGoalPlanNodes" | "updateGoalStatus" | "updateGoalPlanNode" | "recordGoalEvent" | "listGoalRuns" | "createWorkflowRun" | "getWorkflowRun" | "requireWorkflowRun" | "listWorkflowRuns" | "listWorkflowStepRuns" | "getWorkflowStepRun" | "isWorkflowRunTerminal" | "startWorkflowStepRun" | "markWorkflowStepPid" | "recordWorkflowStepProgress" | "recoverWorkflowRun" | "finalizeWorkflowStepRun" | "skipWorkflowStepRun" | "finalizeWorkflowRun" | "appendWorkflowEvent" | "listWorkflowEvents" | "recordRunProcess" | "createSkippedRun" | "getRun" | "getRunBySlot" | "nextRetryableRun" | "claimRun" | "finalizeRun" | "heartbeatRunLease" | "listRuns" | "listRecoveredLeaseRunsPage" | "writeRunReceipt" | "getRunReceipt" | "listRunReceipts" | "recoverExpiredRunLeases" | "recoverExpiredRunLeasesDetailed" | "countLoops" | "countRuns" | "pruneHistory" | "acquireDaemonLease" | "heartbeatDaemonLease" | "releaseDaemonLease" | "getDaemonLease";
|
|
4
4
|
type AsyncStoreMethod<K extends LoopStorageMethodName> = Store[K] extends (...args: infer Args) => infer Result ? (...args: Args) => Promise<Result> : never;
|
|
5
5
|
export interface LoopStorageContract extends Record<LoopStorageMethodName, (...args: never[]) => Promise<unknown>> {
|
|
6
6
|
readonly backend: LoopStorageBackend;
|
|
@@ -10,9 +10,12 @@ export interface LoopStorageContract extends Record<LoopStorageMethodName, (...a
|
|
|
10
10
|
getLoop: AsyncStoreMethod<"getLoop">;
|
|
11
11
|
findLoopByName: AsyncStoreMethod<"findLoopByName">;
|
|
12
12
|
requireLoop: AsyncStoreMethod<"requireLoop">;
|
|
13
|
+
requireUniqueLoop: AsyncStoreMethod<"requireUniqueLoop">;
|
|
13
14
|
listLoops: AsyncStoreMethod<"listLoops">;
|
|
14
15
|
dueLoops: AsyncStoreMethod<"dueLoops">;
|
|
15
16
|
updateLoop: AsyncStoreMethod<"updateLoop">;
|
|
17
|
+
advanceLoopIfCurrent: AsyncStoreMethod<"advanceLoopIfCurrent">;
|
|
18
|
+
tripCircuitBreakerIfCurrent: AsyncStoreMethod<"tripCircuitBreakerIfCurrent">;
|
|
16
19
|
renameLoop: AsyncStoreMethod<"renameLoop">;
|
|
17
20
|
archiveLoop: AsyncStoreMethod<"archiveLoop">;
|
|
18
21
|
unarchiveLoop: AsyncStoreMethod<"unarchiveLoop">;
|
|
@@ -22,6 +25,7 @@ export interface LoopStorageContract extends Record<LoopStorageMethodName, (...a
|
|
|
22
25
|
upsertMigrationWorkflow: AsyncStoreMethod<"upsertMigrationWorkflow">;
|
|
23
26
|
createWorkflow: AsyncStoreMethod<"createWorkflow">;
|
|
24
27
|
getWorkflow: AsyncStoreMethod<"getWorkflow">;
|
|
28
|
+
requireWorkflow: AsyncStoreMethod<"requireWorkflow">;
|
|
25
29
|
listWorkflows: AsyncStoreMethod<"listWorkflows">;
|
|
26
30
|
countWorkflows: AsyncStoreMethod<"countWorkflows">;
|
|
27
31
|
archiveWorkflow: AsyncStoreMethod<"archiveWorkflow">;
|
|
@@ -35,6 +39,8 @@ export interface LoopStorageContract extends Record<LoopStorageMethodName, (...a
|
|
|
35
39
|
admitWorkflowWorkItem: AsyncStoreMethod<"admitWorkflowWorkItem">;
|
|
36
40
|
createGoal: AsyncStoreMethod<"createGoal">;
|
|
37
41
|
getGoal: AsyncStoreMethod<"getGoal">;
|
|
42
|
+
requireGoal: AsyncStoreMethod<"requireGoal">;
|
|
43
|
+
findGoalByContext: AsyncStoreMethod<"findGoalByContext">;
|
|
38
44
|
listGoals: AsyncStoreMethod<"listGoals">;
|
|
39
45
|
createGoalPlanNodes: AsyncStoreMethod<"createGoalPlanNodes">;
|
|
40
46
|
listGoalPlanNodes: AsyncStoreMethod<"listGoalPlanNodes">;
|
|
@@ -44,12 +50,17 @@ export interface LoopStorageContract extends Record<LoopStorageMethodName, (...a
|
|
|
44
50
|
listGoalRuns: AsyncStoreMethod<"listGoalRuns">;
|
|
45
51
|
createWorkflowRun: AsyncStoreMethod<"createWorkflowRun">;
|
|
46
52
|
getWorkflowRun: AsyncStoreMethod<"getWorkflowRun">;
|
|
53
|
+
requireWorkflowRun: AsyncStoreMethod<"requireWorkflowRun">;
|
|
47
54
|
listWorkflowRuns: AsyncStoreMethod<"listWorkflowRuns">;
|
|
48
55
|
listWorkflowStepRuns: AsyncStoreMethod<"listWorkflowStepRuns">;
|
|
49
56
|
getWorkflowStepRun: AsyncStoreMethod<"getWorkflowStepRun">;
|
|
57
|
+
isWorkflowRunTerminal: AsyncStoreMethod<"isWorkflowRunTerminal">;
|
|
50
58
|
startWorkflowStepRun: AsyncStoreMethod<"startWorkflowStepRun">;
|
|
59
|
+
markWorkflowStepPid: AsyncStoreMethod<"markWorkflowStepPid">;
|
|
60
|
+
recordWorkflowStepProgress: AsyncStoreMethod<"recordWorkflowStepProgress">;
|
|
51
61
|
recoverWorkflowRun: AsyncStoreMethod<"recoverWorkflowRun">;
|
|
52
62
|
finalizeWorkflowStepRun: AsyncStoreMethod<"finalizeWorkflowStepRun">;
|
|
63
|
+
skipWorkflowStepRun: AsyncStoreMethod<"skipWorkflowStepRun">;
|
|
53
64
|
finalizeWorkflowRun: AsyncStoreMethod<"finalizeWorkflowRun">;
|
|
54
65
|
appendWorkflowEvent: AsyncStoreMethod<"appendWorkflowEvent">;
|
|
55
66
|
listWorkflowEvents: AsyncStoreMethod<"listWorkflowEvents">;
|
|
@@ -57,10 +68,12 @@ export interface LoopStorageContract extends Record<LoopStorageMethodName, (...a
|
|
|
57
68
|
createSkippedRun: AsyncStoreMethod<"createSkippedRun">;
|
|
58
69
|
getRun: AsyncStoreMethod<"getRun">;
|
|
59
70
|
getRunBySlot: AsyncStoreMethod<"getRunBySlot">;
|
|
71
|
+
nextRetryableRun: AsyncStoreMethod<"nextRetryableRun">;
|
|
60
72
|
claimRun: AsyncStoreMethod<"claimRun">;
|
|
61
73
|
finalizeRun: AsyncStoreMethod<"finalizeRun">;
|
|
62
74
|
heartbeatRunLease: AsyncStoreMethod<"heartbeatRunLease">;
|
|
63
75
|
listRuns: AsyncStoreMethod<"listRuns">;
|
|
76
|
+
listRecoveredLeaseRunsPage: AsyncStoreMethod<"listRecoveredLeaseRunsPage">;
|
|
64
77
|
writeRunReceipt: AsyncStoreMethod<"writeRunReceipt">;
|
|
65
78
|
getRunReceipt: AsyncStoreMethod<"getRunReceipt">;
|
|
66
79
|
listRunReceipts: AsyncStoreMethod<"listRunReceipts">;
|
|
@@ -100,6 +113,7 @@ export interface SchemaMigrationStorage {
|
|
|
100
113
|
listAppliedMigrations(): Promise<AppliedStorageMigration[]>;
|
|
101
114
|
migrate(opts?: {
|
|
102
115
|
dryRun?: boolean;
|
|
116
|
+
through?: string;
|
|
103
117
|
}): Promise<StorageMigrationResult>;
|
|
104
118
|
close(): Promise<void>;
|
|
105
119
|
}
|
|
@@ -2,6 +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,
|
|
5
|
+
export { PostgresLoopStorage, createPostgresLoopStorage, } from "./postgres-loop-storage.js";
|
|
6
6
|
export { POSTGRES_MIGRATION_LEDGER_TABLE, POSTGRES_STORAGE_MIGRATIONS, checksumStorageSql, } from "./postgres-schema.js";
|
|
7
7
|
export { Store } from "../store.js";
|