@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
@@ -0,0 +1,52 @@
1
+ import type { Loop, LoopRun } from "../types.js";
2
+ export declare const MAX_RETRY_DELAY_MS: number;
3
+ export declare const DEFAULT_CIRCUIT_BREAKER_THRESHOLD = 5;
4
+ export declare const CIRCUIT_BREAKER_REASON_PREFIX = "circuit breaker open";
5
+ export type CircuitBreakerThreshold = number | ((loop: Loop) => number | undefined);
6
+ export type LoopAdvancementPatch = Partial<Pick<Loop, "status" | "nextRunAt" | "retryScheduledFor">>;
7
+ export type LoopAdvancementPlan = {
8
+ kind: "none";
9
+ reason: "running" | "missing" | "inactive" | "archived" | "stale" | "already_applied";
10
+ } | {
11
+ kind: "update";
12
+ reason: "retry" | "deferred_retry" | "recurrence";
13
+ patch: LoopAdvancementPatch;
14
+ } | {
15
+ kind: "circuit_breaker";
16
+ failures: number;
17
+ reason: string;
18
+ markerScheduledFor: string;
19
+ patch: LoopAdvancementPatch;
20
+ };
21
+ export declare function loopAdvancementPatchMatchesCurrent(current: Loop, patch: LoopAdvancementPatch): boolean;
22
+ /**
23
+ * Exponential retry backoff with jitter:
24
+ * delay = retryDelayMs * 2^(attempt-1) * (0.5 + random), capped at 6h.
25
+ * Provider-gated failures back off 4x harder.
26
+ */
27
+ export declare function retryBackoffDelayMs(loop: Loop, run: LoopRun, random?: () => number): number;
28
+ export declare function resolveBreakerThreshold(loop: Loop, override?: CircuitBreakerThreshold): number;
29
+ /**
30
+ * Count consecutive final failures from newest-first run history.
31
+ *
32
+ * Retryable failures are neutral, a success resets the streak, and the newest
33
+ * circuit-breaker marker is a watermark so a manual resume starts a fresh
34
+ * streak.
35
+ */
36
+ export declare function consecutiveFailureCountFromRuns(runs: readonly LoopRun[], maxAttempts?: number): number;
37
+ /**
38
+ * Pure scheduling policy shared by the local scheduler and hosted API.
39
+ * Callers gather current loop state, deferred retry state, and recent history,
40
+ * then apply the returned storage mutations using their native sync/async API.
41
+ */
42
+ export declare function planLoopAdvancement(input: {
43
+ current: Loop | undefined;
44
+ run: LoopRun;
45
+ finishedAt: Date;
46
+ succeeded: boolean;
47
+ deferredRetry?: LoopRun;
48
+ retryIntentRun?: LoopRun;
49
+ recentRuns?: readonly LoopRun[];
50
+ retryRandom?: number;
51
+ circuitBreakerThreshold?: CircuitBreakerThreshold;
52
+ }): LoopAdvancementPlan;
@@ -1,8 +1,10 @@
1
- import type { AgentProvider, AgentSandbox, AgentTarget } from "../types.js";
1
+ import type { AgentAllowlistEnforcement, AgentProvider, AgentSandbox, AgentSessionContract, AgentTarget, WorkflowStep } from "../types.js";
2
2
  export type ProviderPromptChannel = "stdin" | "argv";
3
3
  export interface ProviderCapabilities {
4
4
  /** Sandbox values the provider CLI accepts; empty when sandboxing is unsupported. */
5
5
  sandbox: readonly AgentSandbox[];
6
+ /** Tool/command restrictions exposed by this adapter. Metadata-only means advisory, not provider-enforced. */
7
+ allowlist: ProviderAllowlistCapabilities;
6
8
  /** Whether the provider runs as a durable background agent instead of a one-shot process. */
7
9
  durable: boolean;
8
10
  /** Whether the provider can be dispatched to a remote machine transport. */
@@ -10,6 +12,10 @@ export interface ProviderCapabilities {
10
12
  /** How the prompt reaches the provider process. */
11
13
  promptChannel: ProviderPromptChannel;
12
14
  }
15
+ export interface ProviderAllowlistCapabilities {
16
+ tools: AgentAllowlistEnforcement;
17
+ commands: AgentAllowlistEnforcement;
18
+ }
13
19
  export interface AgentInvocation {
14
20
  command: string;
15
21
  args: string[];
@@ -18,16 +24,28 @@ export interface AgentInvocation {
18
24
  /** Executables of which at least one must exist besides the command itself. */
19
25
  preflightAnyOf?: string[];
20
26
  }
27
+ export interface PreparedAgentInvocation {
28
+ /** Invocation built from the first validated snapshot. */
29
+ invocation: AgentInvocation;
30
+ /** Rebuild only cwd-dependent fields while reusing that same snapshot. */
31
+ forCwd(cwd: string): AgentInvocation;
32
+ }
21
33
  export interface ProviderAdapter {
22
34
  provider: AgentProvider;
23
35
  capabilities: ProviderCapabilities;
24
36
  validate(target: AgentTarget, label?: string): void;
25
37
  buildInvocation(target: AgentTarget): AgentInvocation;
38
+ prepareInvocation(target: AgentTarget): PreparedAgentInvocation;
26
39
  }
27
- export declare const UNSAFE_CODEWITH_EXEC_EXTRA_ARGS: Set<string>;
40
+ export declare function effectiveAgentSandbox(target: AgentTarget): AgentSandbox | "provider-default";
41
+ export declare function agentSessionContract(target: AgentTarget, cwd?: string | undefined): AgentSessionContract | undefined;
42
+ export declare function workflowStepAgentSessionContract(step: WorkflowStep): AgentSessionContract | undefined;
43
+ export declare function agentSessionContractPrompt(target: AgentTarget, cwd?: string | undefined): string | undefined;
28
44
  export declare const PROVIDER_ADAPTERS: Record<AgentProvider, ProviderAdapter>;
29
45
  export declare const AGENT_PROVIDERS: AgentProvider[];
30
46
  export declare function providerAdapter(provider: AgentProvider): ProviderAdapter;
47
+ /** Validate an untrusted or persisted agent target without relying on TypeScript-only shape guarantees. */
48
+ export declare function validateAgentTarget(target: unknown, label?: string): asserts target is AgentTarget;
31
49
  export interface SpawnCaptureOptions {
32
50
  cwd?: string;
33
51
  env?: NodeJS.ProcessEnv;
@@ -0,0 +1,14 @@
1
+ export type TenantRole = "admin" | "operator" | "member" | "readonly" | "service" | "worker";
2
+ export type TokenKind = "api_key" | "service" | "machine";
3
+ export interface RoutePolicy {
4
+ operationId: string;
5
+ method: string;
6
+ pathTemplate: string;
7
+ path: RegExp;
8
+ scopes: readonly string[];
9
+ roles: readonly TenantRole[];
10
+ tokenKinds: readonly TokenKind[];
11
+ risk: "read" | "write" | "destructive" | "runner";
12
+ }
13
+ export declare const ROUTE_POLICIES: readonly RoutePolicy[];
14
+ export declare function routePolicy(method: string, path: string): RoutePolicy | undefined;
@@ -0,0 +1,38 @@
1
+ import { type ApiKeyClaims } from "@hasna/contracts/auth";
2
+ import type { TypedQueryClient } from "../../generated/storage-kit/query.js";
3
+ import type { RoutePolicy, TenantRole, TokenKind } from "./route-policy.js";
4
+ export interface TenantAuthContext {
5
+ tenantId: string;
6
+ principalId: string;
7
+ requestId: string;
8
+ kid: string;
9
+ agent: string | null;
10
+ scopes: string[];
11
+ roles: TenantRole[];
12
+ tokenKind: TokenKind;
13
+ claims: ApiKeyClaims;
14
+ }
15
+ export type TenantAuthDecision = {
16
+ ok: true;
17
+ status: 200;
18
+ principal: TenantAuthContext;
19
+ } | {
20
+ ok: false;
21
+ status: 401 | 403 | 503;
22
+ reason: string;
23
+ message: string;
24
+ requestId: string;
25
+ };
26
+ export declare class TenantApiAuthenticator {
27
+ private readonly client;
28
+ private readonly signingSecret;
29
+ private readonly now;
30
+ constructor(client: TypedQueryClient, signingSecret: string | Buffer, now?: () => number);
31
+ authenticate(headers: Headers, context: {
32
+ method: string;
33
+ path: string;
34
+ policy: RoutePolicy;
35
+ }): Promise<TenantAuthDecision>;
36
+ private deny;
37
+ private audit;
38
+ }
@@ -1,16 +1,10 @@
1
- export type StorageMode = "local" | "cloud";
2
- /** Deprecated mode aliases that all normalize to `cloud`. */
3
- export declare const DEPRECATED_STORAGE_MODE_ALIASES: readonly ["remote", "hybrid", "self_hosted"];
1
+ export type StorageMode = "local" | "self_hosted" | "cloud";
4
2
  export type Env = Record<string, string | undefined>;
5
3
  export interface StorageModeNormalization {
6
4
  mode: StorageMode;
7
- /** The deprecated alias that was normalized to `cloud`, if any. */
8
- deprecatedAlias: string | null;
9
5
  }
10
6
  /**
11
- * Normalize a raw storage-mode string to the `local | cloud` runtime enum.
12
- * Accepts deprecated aliases (`remote`, `hybrid`, `self_hosted`) and maps them
13
- * to `cloud`. Throws on any other value.
7
+ * Normalize a raw storage-mode string to the canonical deployment vocabulary.
14
8
  */
15
9
  export declare function normalizeStorageMode(value: string): StorageModeNormalization;
16
10
  /** Upper-snake env token for an app name, e.g. `todos` -> `TODOS`. */
@@ -76,7 +76,6 @@ export type ResolveStorageClientResult = {
76
76
  * `name`; when it resolves to `cloud-http` (mode=cloud/self_hosted + API_URL +
77
77
  * API_KEY), returns a ready {@link HasnaStorageClient}. Otherwise returns
78
78
  * `{ transport: 'local', client: null }` so the app uses its local store.
79
- * Throws if cloud was requested but is misconfigured (so callers never silently
80
- * read the wrong dataset).
79
+ * Incomplete remote configuration throws so callers never read the wrong dataset.
81
80
  */
82
81
  export declare function resolveStorageClient(name: string, env?: Env, overrides?: Parameters<typeof createClientTransport>[2]): ResolveStorageClientResult;
@@ -1,7 +1,5 @@
1
1
  import { type Env } from "./mode.js";
2
2
  import type { StorageMode } from "./mode.js";
3
- /** Default cloud host template. `<app>` is the app slug. */
4
- export declare function defaultCloudBaseUrl(name: string): string;
5
3
  export interface ClientTransportEnvKeys {
6
4
  /** Mode keys, in precedence order. */
7
5
  modeKeys: string[];
@@ -18,10 +16,8 @@ export type ClientTransportKind = "local" | "cloud-http";
18
16
  export interface ClientTransportResolution {
19
17
  /** Where the client should read/write from. */
20
18
  transport: ClientTransportKind;
21
- /** Resolved storage mode (`local` | `cloud`). */
19
+ /** Resolved canonical deployment mode. */
22
20
  mode: StorageMode;
23
- /** Deprecated mode alias that was normalized (e.g. `self_hosted`), if any. */
24
- deprecatedAlias: string | null;
25
21
  /** Env key the mode was read from, or `"default"`. */
26
22
  modeSource: string;
27
23
  /** `<origin>/v1` base for the cloud API when transport is cloud-http, else null. */
@@ -32,20 +28,11 @@ export interface ClientTransportResolution {
32
28
  apiKeyPresent: boolean;
33
29
  /** Env key the API key came from, or null. */
34
30
  apiKeySource: string | null;
35
- /**
36
- * True when the operator asked for cloud but the config is incomplete (no API
37
- * key), so we fell back to local. Callers SHOULD treat this as an error rather
38
- * than silently reading local data.
39
- */
40
- misconfigured: boolean;
41
- /** Human-readable warning, or null. Never contains secret values. */
42
- warning: string | null;
43
31
  }
44
32
  /**
45
33
  * Resolve how a client should reach an app's data given the environment.
46
34
  *
47
- * Precedence for the mode: the first present of `HASNA_<NAME>_STORAGE_MODE`,
48
- * `HASNA_<NAME>_MODE`, `<NAME>_STORAGE_MODE`, `<NAME>_MODE`, else `local`.
35
+ * The only mode key is `HASNA_<NAME>_STORAGE_MODE`; unset means `local`.
49
36
  */
50
37
  export declare function resolveClientTransport(name: string, env?: Env): ClientTransportResolution;
51
38
  /** Thrown when a cloud HTTP request returns a non-2xx status. */
@@ -132,9 +119,8 @@ export declare function createHasnaHttpTransport(options: HasnaHttpTransportOpti
132
119
  /**
133
120
  * Convenience: resolve transport from env and, when cloud-http, build the HTTP
134
121
  * client in one call. Returns `{ transport: 'local', resolution }` for local, or
135
- * `{ transport: 'cloud-http', client, resolution }` for cloud. Throws if the
136
- * config is `misconfigured` (cloud requested but unusable) so callers can't drift
137
- * onto local data by accident.
122
+ * `{ transport: 'cloud-http', client, resolution }` for self_hosted/cloud.
123
+ * Incomplete remote configuration throws before a client is built.
138
124
  */
139
125
  export declare function createClientTransport(name: string, env?: Env, overrides?: Partial<Pick<HasnaHttpTransportOptions, "fetchImpl" | "headers" | "timeoutMs" | "retry" | "sleepImpl">>): {
140
126
  transport: "local";
@@ -13,9 +13,51 @@ export declare class LoopNotFoundError extends CodedError {
13
13
  export declare class LoopArchivedError extends CodedError {
14
14
  constructor(idOrName: string);
15
15
  }
16
+ export declare class LoopAdvancementConflictError extends CodedError {
17
+ constructor(loopId: string, runId: string);
18
+ }
19
+ export type RunFinalizationConflictReason = "stale_claim" | "run_not_running";
20
+ export declare class RunFinalizationConflictError extends CodedError {
21
+ readonly reason: RunFinalizationConflictReason;
22
+ constructor(reason: RunFinalizationConflictReason, runId: string);
23
+ }
16
24
  export declare class AmbiguousNameError extends CodedError {
17
25
  constructor(name: string);
18
26
  }
27
+ export type AgentExtraArgsValidationReason = "not_array" | "invalid_array" | "invalid_item" | "option_not_allowed";
28
+ /**
29
+ * Deliberately public, bounded validation metadata. API boundaries may expose
30
+ * this object, but must never expose the accompanying Error.message.
31
+ */
32
+ export interface PublicValidationDetails {
33
+ code: "agent_extra_args_invalid";
34
+ reason: AgentExtraArgsValidationReason;
35
+ path: string;
36
+ index?: number;
37
+ option?: string;
38
+ }
39
+ export declare function publicValidationDetails(value: unknown): Readonly<PublicValidationDetails> | undefined;
19
40
  export declare class ValidationError extends CodedError {
20
- constructor(message: string);
41
+ readonly publicDetails?: Readonly<PublicValidationDetails>;
42
+ constructor(message: string, publicDetails?: PublicValidationDetails);
43
+ }
44
+ /** Safely capture and re-project even forged/subclass validation errors. */
45
+ export declare function validationErrorPublicDetails(error: ValidationError): Readonly<PublicValidationDetails> | undefined;
46
+ export declare class DuplicateWorkflowEventError extends CodedError {
47
+ constructor(workflowRunId: string, eventType: string, stepId?: string);
48
+ }
49
+ export declare class LegacyWorkflowRunProvenanceError extends CodedError {
50
+ constructor(workflowRunId: string);
51
+ }
52
+ export declare class WorkflowRunDefinitionConflictError extends CodedError {
53
+ constructor(workflowRunId: string);
54
+ }
55
+ export declare class WorkflowRunHasLiveStepsError extends CodedError {
56
+ constructor();
57
+ }
58
+ export declare class WorkflowRunStepOwnershipUnverifiableError extends CodedError {
59
+ constructor();
60
+ }
61
+ export declare class WorkflowRunNotRunningError extends CodedError {
62
+ constructor();
21
63
  }
@@ -56,6 +56,15 @@ interface MachineCommandPlan {
56
56
  }
57
57
  /** Exported for tests: resolves the default idle watchdog for agent targets. */
58
58
  export declare function defaultAgentIdleTimeoutMs(target: AgentTarget, opts: ExecuteOptions): number | undefined;
59
+ /**
60
+ * Detects git's "missing but already registered worktree" error: the target
61
+ * path is recorded in `.git/worktrees/` but its directory was deleted out from
62
+ * under it, so `git worktree add` refuses. git's own prescribed remedy is
63
+ * `git worktree prune` (or `add -f`). Left unhandled this terminal-fails worktree
64
+ * prep on every attempt and the drain dedupes the failed item into a silent
65
+ * wedge — historically the single biggest burner of the redispatch cap.
66
+ */
67
+ export declare function isStaleWorktreeRegistration(detail: string | undefined): boolean;
59
68
  export declare function preflightTarget(target: ExecutableTarget, metadata?: ExecutionMetadata, opts?: ExecuteOptions): PreflightResult;
60
69
  export declare function executeTarget(target: ExecutableTarget, metadata?: ExecutionMetadata, opts?: ExecuteOptions): Promise<ExecutorResult>;
61
70
  export declare function executeLoop(loop: Loop, run: LoopRun, opts?: ExecuteOptions): Promise<ExecutorResult>;
@@ -1,4 +1,4 @@
1
- import type { ExecutorResult, Goal, GoalRun, Loop, LoopRun, RunReceipt, WorkflowEvent, WorkflowInvocation, WorkflowRun, WorkflowSpec, WorkflowStepRun, WorkflowWorkItem } from "../types.js";
1
+ import type { ExecutorResult, Goal, GoalRun, Loop, LoopRun, RunReceipt, PublicWorkflowEvent, StoredWorkflowEvent, WorkflowInvocation, WorkflowRun, WorkflowSpec, WorkflowStepRun, WorkflowWorkItem } from "../types.js";
2
2
  export declare function redact(value: string | undefined, visible?: number): string | undefined;
3
3
  export declare function textOutputBlocks(value: Pick<LoopRun | WorkflowStepRun, "stdout" | "stderr">, opts?: {
4
4
  indent?: string;
@@ -14,6 +14,6 @@ export declare function publicWorkflowRun(run: WorkflowRun): Record<string, unkn
14
14
  export declare function publicWorkflowInvocation(invocation: WorkflowInvocation): Record<string, unknown>;
15
15
  export declare function publicWorkflowWorkItem(item: WorkflowWorkItem): Record<string, unknown>;
16
16
  export declare function publicWorkflowStepRun(run: WorkflowStepRun, showOutput?: boolean): Record<string, unknown>;
17
- export declare function publicWorkflowEvent(event: WorkflowEvent): Record<string, unknown>;
17
+ export declare function publicWorkflowEvent(event: StoredWorkflowEvent | PublicWorkflowEvent): Record<string, unknown>;
18
18
  export declare function publicGoal(goal: Goal): Record<string, unknown>;
19
19
  export declare function publicGoalRun(run: GoalRun): Record<string, unknown>;
@@ -1,3 +1,36 @@
1
- import type { Store } from "../store.js";
2
- import type { GoalExecutorResult, GoalSpec, RunGoalOptions } from "./types.js";
3
- export declare function runGoal(store: Store, input: GoalSpec, opts?: RunGoalOptions): Promise<GoalExecutorResult>;
1
+ import type { CreateGoalInput, CreateGoalPlanNodeInput, RecordGoalEventInput } from "../store.js";
2
+ import type { Goal, GoalExecutorResult, GoalPlanNode, GoalRun, GoalSpec, GoalStatus, RunGoalOptions } from "./types.js";
3
+ type MaybePromise<T> = T | Promise<T>;
4
+ export interface GoalRunnerStore {
5
+ findGoalByContext(context: {
6
+ loopRunId?: string;
7
+ workflowRunId?: string;
8
+ workflowStepId?: string;
9
+ sourceType?: string;
10
+ sourceId?: string;
11
+ }): MaybePromise<Goal | undefined>;
12
+ createGoal(input: CreateGoalInput, opts?: {
13
+ daemonLeaseId?: string;
14
+ now?: Date;
15
+ }): MaybePromise<Goal>;
16
+ requireGoal(id: string): MaybePromise<Goal>;
17
+ createGoalPlanNodes(goalId: string, nodes: CreateGoalPlanNodeInput[], opts?: {
18
+ daemonLeaseId?: string;
19
+ now?: Date;
20
+ }): MaybePromise<GoalPlanNode[]>;
21
+ listGoalPlanNodes(goalIdOrPlanId: string): MaybePromise<GoalPlanNode[]>;
22
+ updateGoalStatus(goalId: string, status: GoalStatus, opts?: {
23
+ daemonLeaseId?: string;
24
+ now?: Date;
25
+ }): MaybePromise<Goal>;
26
+ updateGoalPlanNode(goalId: string, key: string, patch: Partial<Pick<GoalPlanNode, "status" | "tokensUsed" | "timeUsedSeconds" | "ready">>, opts?: {
27
+ daemonLeaseId?: string;
28
+ now?: Date;
29
+ }): MaybePromise<GoalPlanNode>;
30
+ recordGoalEvent(input: RecordGoalEventInput, opts?: {
31
+ daemonLeaseId?: string;
32
+ now?: Date;
33
+ }): MaybePromise<GoalRun>;
34
+ }
35
+ export declare function runGoal(store: GoalRunnerStore, input: GoalSpec, opts?: RunGoalOptions): Promise<GoalExecutorResult>;
36
+ export {};
@@ -2,7 +2,7 @@ import type { Loop, LoopRun, LoopStatus } from "../types.js";
2
2
  import type { DaemonStatus } from "../daemon/control.js";
3
3
  import type { DoctorCheck, DoctorReport } from "./doctor.js";
4
4
  import type { Store } from "./store.js";
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";
5
+ export type RunFailureClassification = "rate_limit" | "auth" | "provider_capacity" | "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";
6
6
  export interface RunFailureSignal {
7
7
  classification: RunFailureClassification;
8
8
  fingerprint: string;
@@ -0,0 +1,4 @@
1
+ export declare const LOOP_LABEL_MAX_COUNT = 32;
2
+ export declare function normalizeLoopLabels(value: string | string[] | undefined, label?: string): string[];
3
+ export declare function mergeLoopLabels(current: string[] | undefined, added: string | string[]): string[];
4
+ export declare function removeLoopLabels(current: string[] | undefined, removed: string | string[]): string[];
@@ -0,0 +1,4 @@
1
+ import type { LoopStatus } from "../types.js";
2
+ export declare const LOOP_STATUSES: readonly ["active", "paused", "stopped", "expired"];
3
+ export declare function isLoopStatus(value: unknown): value is LoopStatus;
4
+ export declare function assertLoopStatus(value: unknown): asserts value is LoopStatus;
@@ -1,6 +1,7 @@
1
1
  import type { Loop, LoopRun, WorkflowSpec } from "../types.js";
2
2
  import type { Store, StoreMigrationChecks } from "./store.js";
3
3
  export declare const LOOPS_MIGRATION_SCHEMA = "open-loops.migration/v1";
4
+ export declare const LOOPS_SELF_HOSTED_PUSH_MANIFEST_SCHEMA = "open-loops.self-hosted-push-manifest/v1";
4
5
  export type LoopsMigrationResource = "workflow" | "loop" | "run" | "remote";
5
6
  export type LoopsMigrationAction = "insert" | "update" | "skip" | "conflict" | "blocked";
6
7
  export interface LoopsMigrationBundle {
@@ -67,6 +68,7 @@ export interface LoopsMigrationPlan {
67
68
  summary: LoopsMigrationPlanSummary;
68
69
  rows: LoopsMigrationPlanRow[];
69
70
  warnings: string[];
71
+ manifest?: SelfHostedPushManifest;
70
72
  }
71
73
  export interface ApplyLoopsMigrationResult {
72
74
  plan: LoopsMigrationPlan;
@@ -79,24 +81,12 @@ export interface ApplyLoopsMigrationResult {
79
81
  export interface SelfHostedPlanOptions {
80
82
  operation: "self-hosted-push" | "self-hosted-pull" | "self-hosted-migrate";
81
83
  apiUrl?: string;
82
- apiToken?: string;
84
+ apiKey?: string;
85
+ timeoutMs?: number;
83
86
  fetchImpl?: typeof fetch;
84
87
  env?: NodeJS.ProcessEnv;
85
88
  includeRuns?: boolean;
86
- }
87
- export interface RunnerRegistrationOptions {
88
- apiUrl?: string;
89
- apiToken?: string;
90
- runnerId: string;
91
- machineId?: string;
92
- labels?: Record<string, string>;
93
- capabilities?: Record<string, unknown>;
94
- fetchImpl?: typeof fetch;
95
- env?: NodeJS.ProcessEnv;
96
- }
97
- export interface RunnerRegistrationResult {
98
- ok: boolean;
99
- runner: Record<string, unknown>;
89
+ replace?: boolean;
100
90
  }
101
91
  export declare function migrationHash(value: unknown): string;
102
92
  export declare function exportLoopsMigrationBundle(store: Store, opts?: ExportLoopsMigrationOptions): LoopsMigrationBundle;
@@ -106,7 +96,8 @@ export declare function applyImportMigrationBundle(store: Store, bundle: LoopsMi
106
96
  export declare function buildSelfHostedMigrationPlan(store: Store, opts: SelfHostedPlanOptions): Promise<LoopsMigrationPlan>;
107
97
  export interface SelfHostedPushOptions {
108
98
  apiUrl?: string;
109
- apiToken?: string;
99
+ apiKey?: string;
100
+ timeoutMs?: number;
110
101
  includeRuns?: boolean;
111
102
  replace?: boolean;
112
103
  fetchImpl?: typeof fetch;
@@ -134,6 +125,69 @@ export interface SelfHostedPushResult {
134
125
  orphanRuns: number;
135
126
  };
136
127
  requests: number;
128
+ manifest: SelfHostedPushManifest;
129
+ }
130
+ export interface SelfHostedPushManifest {
131
+ schema: typeof LOOPS_SELF_HOSTED_PUSH_MANIFEST_SCHEMA;
132
+ generatedAt: string;
133
+ apiUrl?: string;
134
+ dryRun: boolean;
135
+ replace: boolean;
136
+ includeRuns: boolean;
137
+ safety: {
138
+ forcedLoopStatus: "paused";
139
+ clearedLoopRunPointers: true;
140
+ forcedWorkflowStatus: "archived";
141
+ resumesLoops: false;
142
+ };
143
+ counts: {
144
+ source: {
145
+ workflows: number;
146
+ loops: number;
147
+ runs: number;
148
+ };
149
+ remoteBefore: {
150
+ workflows?: number;
151
+ loops?: number;
152
+ runs?: number;
153
+ };
154
+ remoteAfter?: {
155
+ workflows?: number;
156
+ loops?: number;
157
+ runs?: number;
158
+ };
159
+ plan: LoopsMigrationPlanSummary;
160
+ applied?: {
161
+ workflows: number;
162
+ loops: number;
163
+ runs: number;
164
+ };
165
+ skipped?: {
166
+ runningRuns: number;
167
+ orphanRuns: number;
168
+ };
169
+ requests?: number;
170
+ };
171
+ missingIds: {
172
+ workflows: string[];
173
+ loops: string[];
174
+ runs: string[];
175
+ };
176
+ existingIds: {
177
+ workflows: string[];
178
+ loops: string[];
179
+ runs: string[];
180
+ };
181
+ unsafe: {
182
+ blocked: LoopsMigrationPlanRow[];
183
+ conflicts: LoopsMigrationPlanRow[];
184
+ unsupported: string[];
185
+ warnings: string[];
186
+ };
187
+ rollback: {
188
+ notes: string[];
189
+ commands: string[];
190
+ };
137
191
  }
138
192
  /**
139
193
  * Apply a local->self-hosted backfill through the control plane's id-preserving
@@ -144,6 +198,5 @@ export interface SelfHostedPushResult {
144
198
  * endpoint upserts by id, so re-running never duplicates rows.
145
199
  */
146
200
  export declare function applySelfHostedPush(store: Store, opts: SelfHostedPushOptions): Promise<SelfHostedPushResult>;
147
- export declare function registerSelfHostedRunner(opts: RunnerRegistrationOptions): Promise<RunnerRegistrationResult>;
148
201
  export declare function publicMigrationBundle(bundle: LoopsMigrationBundle): Record<string, unknown>;
149
202
  export declare function selfHostedControlPlaneSummary(env?: NodeJS.ProcessEnv): Record<string, unknown>;
@@ -11,11 +11,8 @@ export interface LoopModeResolution {
11
11
  }
12
12
  export interface LoopControlPlaneConfig {
13
13
  apiUrl?: string;
14
- cloudApiUrl?: string;
15
14
  databaseUrlPresent: boolean;
16
- apiAuthTokenPresent: boolean;
17
- cloudAuthTokenPresent: boolean;
18
- authTokenPresent: boolean;
15
+ apiKeyPresent: boolean;
19
16
  }
20
17
  export interface LoopDeploymentStatus {
21
18
  packageVersion: string;
@@ -32,7 +29,7 @@ export interface LoopDeploymentStatus {
32
29
  configured: boolean;
33
30
  apiUrl?: string;
34
31
  databaseUrlPresent: boolean;
35
- authTokenPresent: boolean;
32
+ apiKeyPresent: boolean;
36
33
  };
37
34
  runner: {
38
35
  required: boolean;