@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.
Files changed (79) hide show
  1. package/CHANGELOG.md +205 -1
  2. package/README.md +232 -49
  3. package/dist/api/index.d.ts +20 -19
  4. package/dist/api/index.js +7770 -1342
  5. package/dist/cli/index.js +2976 -966
  6. package/dist/cli/safe-error-context.d.ts +1 -0
  7. package/dist/daemon/daemon.d.ts +1 -0
  8. package/dist/daemon/index.js +1899 -499
  9. package/dist/generated/storage-kit/index.d.ts +1 -1
  10. package/dist/generated/storage-kit/mode.d.ts +6 -12
  11. package/dist/index.d.ts +3 -3
  12. package/dist/index.js +5319 -1096
  13. package/dist/lib/advancement.d.ts +52 -0
  14. package/dist/lib/agent-adapter.d.ts +20 -2
  15. package/dist/lib/auth/route-policy.d.ts +14 -0
  16. package/dist/lib/auth/tenant-auth.d.ts +38 -0
  17. package/dist/lib/cloud/mode.d.ts +2 -8
  18. package/dist/lib/cloud/storage.d.ts +1 -2
  19. package/dist/lib/cloud/transport.d.ts +4 -18
  20. package/dist/lib/errors.d.ts +43 -1
  21. package/dist/lib/executor.d.ts +9 -0
  22. package/dist/lib/format.d.ts +2 -2
  23. package/dist/lib/goal/runner.d.ts +36 -3
  24. package/dist/lib/health.d.ts +1 -1
  25. package/dist/lib/labels.d.ts +4 -0
  26. package/dist/lib/loop-status.d.ts +4 -0
  27. package/dist/lib/migration.d.ts +70 -17
  28. package/dist/lib/mode.d.ts +2 -5
  29. package/dist/lib/mode.js +28 -37
  30. package/dist/lib/route/fields.d.ts +8 -0
  31. package/dist/lib/route/index.d.ts +2 -2
  32. package/dist/lib/route/throttle.d.ts +7 -0
  33. package/dist/lib/route/types.d.ts +3 -0
  34. package/dist/lib/run-completion.d.ts +18 -0
  35. package/dist/lib/scheduler.d.ts +5 -11
  36. package/dist/lib/storage/contract.d.ts +15 -1
  37. package/dist/lib/storage/index.d.ts +1 -1
  38. package/dist/lib/storage/index.js +4083 -486
  39. package/dist/lib/storage/pg-executor.d.ts +10 -5
  40. package/dist/lib/storage/postgres-loop-storage.d.ts +52 -26
  41. package/dist/lib/storage/postgres-schema.d.ts +8 -0
  42. package/dist/lib/storage/postgres-schema.js +1326 -1
  43. package/dist/lib/storage/postgres.d.ts +3 -1
  44. package/dist/lib/storage/postgres.js +1356 -27
  45. package/dist/lib/storage/provider-credentials.d.ts +84 -0
  46. package/dist/lib/storage/shared-database-transfer.d.ts +136 -0
  47. package/dist/lib/storage/sqlite.d.ts +15 -2
  48. package/dist/lib/storage/sqlite.js +1379 -217
  49. package/dist/lib/storage/tenant-backfill-s3.d.ts +53 -0
  50. package/dist/lib/storage/tenant-backfill.d.ts +40 -0
  51. package/dist/lib/store/index.d.ts +14 -8
  52. package/dist/lib/store.d.ts +129 -7
  53. package/dist/lib/store.js +1352 -218
  54. package/dist/lib/templates.d.ts +11 -0
  55. package/dist/lib/workflow-events.d.ts +6 -0
  56. package/dist/lib/workflow-provenance.d.ts +8 -0
  57. package/dist/lib/workflow-runner.d.ts +52 -4
  58. package/dist/mcp/index.js +2074 -657
  59. package/dist/runner/index.d.ts +20 -2
  60. package/dist/runner/index.js +2146 -183
  61. package/dist/sdk/http.d.ts +364 -4
  62. package/dist/sdk/http.js +379 -1
  63. package/dist/sdk/index.d.ts +7 -2
  64. package/dist/sdk/index.js +2341 -742
  65. package/dist/serve/index.d.ts +14 -0
  66. package/dist/serve/index.js +13269 -1830
  67. package/dist/types.d.ts +75 -3
  68. package/docs/AUTOMATION_RUNTIME_DESIGN.md +32 -32
  69. package/docs/CUTOVER-RUNBOOK.md +158 -31
  70. package/docs/DEPLOYMENT_MODES.md +84 -56
  71. package/docs/RUNTIME_BOUNDARY.md +26 -26
  72. package/docs/SHARED-DATABASE-TRANSFER.md +95 -0
  73. package/docs/SHARED_KIT_EXTRACTION_INVENTORY.md +631 -0
  74. package/docs/TRANSCRIPT_LOOP_PATTERNS.md +3 -3
  75. package/docs/UNIFIED_PRODUCT_CONTRACT.md +365 -0
  76. package/docs/USAGE.md +150 -56
  77. package/docs/workflows/transcript-feedback-to-loops.json +2 -2
  78. package/package.json +8 -4
  79. 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.27",
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 = ["LOOPS_MODE", "HASNA_LOOPS_MODE"];
148
- var API_URL_ENV_KEYS = ["LOOPS_API_URL", "HASNA_LOOPS_API_URL"];
149
- var CLOUD_API_URL_ENV_KEYS = ["LOOPS_CLOUD_API_URL", "HASNA_LOOPS_CLOUD_API_URL"];
150
- var DATABASE_URL_ENV_KEYS = ["LOOPS_DATABASE_URL", "HASNA_LOOPS_DATABASE_URL"];
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().toLowerCase().replace(/-/g, "_");
164
+ const normalized = value.trim();
164
165
  if (normalized === "local")
165
166
  return "local";
166
- if (normalized === "self_hosted" || normalized === "selfhosted")
167
+ if (normalized === "self_hosted")
167
168
  return "self_hosted";
168
- if (normalized === "cloud" || normalized === "saas")
169
+ if (normalized === "cloud")
169
170
  return "cloud";
170
- throw new Error(`unsupported OpenLoops deployment mode "${value}"; expected local, self_hosted, or cloud`);
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
- apiAuthTokenPresent: Boolean(envValue(env, API_TOKEN_ENV_KEYS)),
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.cloudApiUrl ? "hosted_control_plane_contract" : "unconfigured";
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 rawApiUrl = deploymentMode === "cloud" ? config.cloudApiUrl : config.apiUrl;
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.cloudAuthTokenPresent : deploymentMode === "self_hosted" ? config.apiAuthTokenPresent : false;
272
- const controlPlaneConfigured = deploymentMode === "local" ? false : deploymentMode === "self_hosted" ? Boolean(config.apiUrl || config.databaseUrlPresent) : Boolean(config.cloudApiUrl && deploymentAuthTokenPresent);
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 LOOPS_API_URL or LOOPS_DATABASE_URL before it can become authoritative");
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("LOOPS_DATABASE_URL selects the self_hosted storage contract; loops-runner still needs LOOPS_API_URL to claim remote work");
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 && !config.cloudApiUrl) {
281
- warnings.push("LOOPS_API_URL selects self_hosted; cloud mode uses LOOPS_CLOUD_API_URL");
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" && !config.cloudApiUrl) {
284
- warnings.push("cloud mode is a public contract until LOOPS_CLOUD_API_URL is configured");
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
- authTokenPresent: deploymentAuthTokenPresent
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 OpenLoops workflow loops and todos tasks.
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;
@@ -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?: number | ((loop: Loop) => number | undefined);
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, NotImplementedError, } from "./postgres-loop-storage.js";
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";