@hasna/loops 0.4.14 → 0.4.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +119 -29
  3. package/dist/api/index.d.ts +1 -0
  4. package/dist/api/index.js +825 -7
  5. package/dist/cli/index.js +5419 -2619
  6. package/dist/daemon/index.js +478 -84
  7. package/dist/generated/storage-kit/index.d.ts +1 -1
  8. package/dist/index.js +2521 -226
  9. package/dist/lib/cloud/mode.d.ts +17 -0
  10. package/dist/lib/cloud/resolve.d.ts +16 -0
  11. package/dist/lib/cloud/storage.d.ts +82 -0
  12. package/dist/lib/cloud/transport.d.ts +148 -0
  13. package/dist/lib/format.d.ts +2 -1
  14. package/dist/lib/migration.d.ts +40 -0
  15. package/dist/lib/mode.js +3 -3
  16. package/dist/lib/route/index.d.ts +2 -0
  17. package/dist/lib/route/policies.d.ts +51 -0
  18. package/dist/lib/route/provider-admission.d.ts +37 -0
  19. package/dist/lib/route/throttle.d.ts +4 -0
  20. package/dist/lib/route/types.d.ts +8 -0
  21. package/dist/lib/run-receipts.d.ts +14 -0
  22. package/dist/lib/storage/contract.d.ts +7 -1
  23. package/dist/lib/storage/index.js +710 -31
  24. package/dist/lib/storage/postgres-loop-storage.d.ts +6 -0
  25. package/dist/lib/storage/postgres-schema.js +29 -0
  26. package/dist/lib/storage/postgres.js +29 -0
  27. package/dist/lib/storage/sqlite.d.ts +6 -0
  28. package/dist/lib/storage/sqlite.js +412 -18
  29. package/dist/lib/store/index.d.ts +268 -0
  30. package/dist/lib/store.d.ts +48 -1
  31. package/dist/lib/store.js +396 -18
  32. package/dist/lib/template-kit.d.ts +25 -0
  33. package/dist/lib/templates.d.ts +16 -1
  34. package/dist/mcp/http.d.ts +16 -0
  35. package/dist/mcp/index.js +1658 -168
  36. package/dist/runner/index.js +76 -9
  37. package/dist/sdk/http.d.ts +67 -0
  38. package/dist/sdk/http.js +21 -0
  39. package/dist/sdk/index.d.ts +50 -17
  40. package/dist/sdk/index.js +1509 -132
  41. package/dist/serve/index.js +1598 -122
  42. package/dist/types.d.ts +49 -0
  43. package/docs/AUTOMATION_RUNTIME_DESIGN.md +442 -1
  44. package/docs/CUTOVER-RUNBOOK.md +73 -56
  45. package/docs/DEPLOYMENT_MODES.md +35 -18
  46. package/docs/RUNTIME_BOUNDARY.md +203 -0
  47. package/docs/USAGE.md +145 -27
  48. package/package.json +3 -3
@@ -3,7 +3,7 @@
3
3
  // package.json
4
4
  var package_default = {
5
5
  name: "@hasna/loops",
6
- version: "0.4.14",
6
+ version: "0.4.22",
7
7
  description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
8
8
  type: "module",
9
9
  main: "dist/index.js",
@@ -113,7 +113,6 @@ var package_default = {
113
113
  bun: ">=1.0.0"
114
114
  },
115
115
  dependencies: {
116
- "@hasna/contracts": "^0.4.1",
117
116
  "@hasna/events": "^0.1.9",
118
117
  "@modelcontextprotocol/sdk": "^1.29.0",
119
118
  "@openrouter/ai-sdk-provider": "2.9.1",
@@ -123,7 +122,8 @@ var package_default = {
123
122
  zod: "4.4.3"
124
123
  },
125
124
  optionalDependencies: {
126
- "@hasna/machines": "0.0.49"
125
+ "@hasna/machines": "0.0.49",
126
+ "@hasna/contracts": "^0.4.2"
127
127
  },
128
128
  devDependencies: {
129
129
  "@types/bun": "latest",
@@ -1098,6 +1098,7 @@ var TRANSPORT_ENV_KEYS = new Set([
1098
1098
  "LOGNAME",
1099
1099
  "PATH",
1100
1100
  "SHELL",
1101
+ "SHLVL",
1101
1102
  "SSH_AGENT_PID",
1102
1103
  "SSH_AUTH_SOCK",
1103
1104
  "TERM",
@@ -1319,6 +1320,7 @@ function composeExecutionEnv(spec, metadata, opts, accountEnv) {
1319
1320
  Object.assign(env, spec.env ?? {});
1320
1321
  Object.assign(env, allowlistEnv(spec.allowlist));
1321
1322
  env.PATH = normalizeExecutionPath(env);
1323
+ env.SHLVL ||= "1";
1322
1324
  Object.assign(env, metadataEnv(metadata));
1323
1325
  return env;
1324
1326
  }
@@ -1390,7 +1392,7 @@ function remoteWorktreePrepareLines(worktree) {
1390
1392
  return [
1391
1393
  "__openloops_prepare_worktree() {",
1392
1394
  ` local repo=${shellQuote(repoRoot)} path=${shellQuote(path)} branch=${shellQuote(branch)}`,
1393
- " local top expected_common actual_common current",
1395
+ " local top expected_common actual_common current status recovered",
1394
1396
  ' if [ -L "$path" ]; then echo "refusing symlinked worktree path $path" >&2; return 1; fi',
1395
1397
  ' if [ -e "$path" ]; then',
1396
1398
  ' top="$(git -C "$path" rev-parse --show-toplevel 2>/dev/null)" || { echo "refusing to reuse non-worktree path: $path" >&2; return 1; }',
@@ -1399,7 +1401,19 @@ function remoteWorktreePrepareLines(worktree) {
1399
1401
  ' actual_common="$(cd "$path" 2>/dev/null && cd "$(git rev-parse --git-common-dir 2>/dev/null)" 2>/dev/null && pwd -P)"',
1400
1402
  ' if [ -z "$expected_common" ] || [ "$expected_common" != "$actual_common" ]; then echo "existing worktree $path belongs to a different git common dir" >&2; return 1; fi',
1401
1403
  ' current="$(git -C "$path" branch --show-current 2>/dev/null)"',
1402
- ' if [ "$current" != "$branch" ]; then echo "existing worktree $path is on branch ${current:-unknown}, expected $branch" >&2; return 1; fi',
1404
+ ' if [ "$current" != "$branch" ]; then',
1405
+ ' status="$(git -C "$path" status --porcelain=v1 --untracked-files=all 2>/dev/null)" || { echo "existing worktree $path is on branch ${current:-unknown}, expected $branch, and cleanliness could not be checked; inspect or remove the stale worktree" >&2; return 1; }',
1406
+ ' if [ -n "$status" ]; then echo "existing worktree $path is on branch ${current:-unknown}, expected $branch, and has local changes; commit/stash them or remove the stale worktree before retrying" >&2; return 1; fi',
1407
+ ' if git -C "$repo" show-ref --verify --quiet "refs/heads/$branch"; then',
1408
+ ' git -C "$path" checkout "$branch" 1>&2 || { echo "existing worktree $path is clean but could not switch from branch ${current:-unknown} to expected $branch; remove/prune the stale worktree or free the branch before retrying" >&2; return 1; }',
1409
+ ' elif [ -z "$current" ]; then',
1410
+ ' git -C "$path" checkout -b "$branch" 1>&2 || { echo "existing worktree $path is clean but could not recreate expected branch $branch at the current detached HEAD; remove/prune the stale worktree before retrying" >&2; return 1; }',
1411
+ " else",
1412
+ ' echo "existing worktree $path is on branch $current, expected $branch, but expected branch does not exist; remove/prune the stale worktree or recreate the expected branch before retrying" >&2; return 1',
1413
+ " fi",
1414
+ ' recovered="$(git -C "$path" branch --show-current 2>/dev/null)"',
1415
+ ' if [ "$recovered" != "$branch" ]; then echo "existing worktree $path branch recovery ended on ${recovered:-unknown}, expected $branch; remove/prune the stale worktree before retrying" >&2; return 1; fi',
1416
+ " fi",
1403
1417
  " return 0",
1404
1418
  " fi",
1405
1419
  ' git -C "$repo" rev-parse --is-inside-work-tree >/dev/null 2>&1 || { echo "worktree repoRoot is not a git repository: $repo" >&2; return 1; }',
@@ -1480,8 +1494,21 @@ function transportEnv(opts) {
1480
1494
  env[key] = value;
1481
1495
  }
1482
1496
  env.PATH = normalizeExecutionPath(env);
1497
+ env.SHLVL ||= "1";
1483
1498
  return env;
1484
1499
  }
1500
+ function remotePreflightFailureMessage(machineId, detail) {
1501
+ const normalized = detail.trim();
1502
+ if (/Host key verification failed|REMOTE HOST IDENTIFICATION HAS CHANGED|Offending .*key in .*known_hosts/i.test(normalized)) {
1503
+ return [
1504
+ `remote preflight failed on ${machineId}: SSH host key verification failed.`,
1505
+ `Verify ${machineId}'s host identity and repair SSH known_hosts/trust material outside OpenLoops;`,
1506
+ "OpenLoops will not disable host-key checking or modify known_hosts automatically.",
1507
+ normalized ? `Transport detail: ${normalized}` : ""
1508
+ ].filter(Boolean).join(" ");
1509
+ }
1510
+ return `remote preflight failed on ${machineId}${normalized ? `: ${normalized}` : ""}`;
1511
+ }
1485
1512
  function assertCodewithProfileListed(profile, result) {
1486
1513
  const profiles = codewithProfileSetFromResult(result);
1487
1514
  if (!profiles.has(profile)) {
@@ -1533,6 +1560,9 @@ async function preflightNativeAuthProfile(spec, env) {
1533
1560
  const tableResult = await spawnCapture(spec.command, ["profile", "list"], { env, timeoutMs: 15000 });
1534
1561
  assertCodewithProfileListed(spec.nativeAuthProfile.profile, tableResult);
1535
1562
  }
1563
+ function spawnDetail(result) {
1564
+ return (result.stderr || result.stdout || result.error || "").toString().trim();
1565
+ }
1536
1566
  function resolvedDirEquals(left, right) {
1537
1567
  try {
1538
1568
  return realpathSync(left) === realpathSync(right);
@@ -1582,8 +1612,45 @@ async function ensureLocalWorktree(worktree, env) {
1582
1612
  }
1583
1613
  const current = await git(["-C", path, "branch", "--show-current"]);
1584
1614
  const actualBranch = current.stdout.trim();
1585
- if (current.error || (current.status ?? 1) !== 0 || actualBranch !== branch) {
1586
- return { error: `existing worktree ${path} is on branch ${actualBranch || "unknown"}, expected ${branch}` };
1615
+ if (current.error || (current.status ?? 1) !== 0) {
1616
+ return { error: `existing worktree ${path} branch could not be inspected${spawnDetail(current) ? `: ${spawnDetail(current)}` : ""}` };
1617
+ }
1618
+ if (actualBranch !== branch) {
1619
+ const actualLabel = actualBranch || "unknown";
1620
+ const status = await git(["-C", path, "status", "--porcelain=v1", "--untracked-files=all"]);
1621
+ if (status.error || (status.status ?? 1) !== 0) {
1622
+ const detail = spawnDetail(status);
1623
+ return {
1624
+ error: `existing worktree ${path} is on branch ${actualLabel}, expected ${branch}, and cleanliness could not be checked; inspect or remove the stale worktree${detail ? `: ${detail}` : ""}`
1625
+ };
1626
+ }
1627
+ if (status.stdout.trim()) {
1628
+ return {
1629
+ error: `existing worktree ${path} is on branch ${actualLabel}, expected ${branch}, and has local changes; commit/stash them or remove the stale worktree before retrying`
1630
+ };
1631
+ }
1632
+ const hasBranch2 = await git(["-C", repoRoot, "show-ref", "--verify", "--quiet", `refs/heads/${branch}`]);
1633
+ const checkout = hasBranch2.status === 0 ? await git(["-C", path, "checkout", branch]) : actualBranch ? {
1634
+ status: 1,
1635
+ stdout: "",
1636
+ stderr: `existing worktree ${path} is on branch ${actualLabel}, expected ${branch}, but expected branch does not exist; remove/prune the stale worktree or recreate the expected branch before retrying`,
1637
+ signal: null,
1638
+ timedOut: false
1639
+ } : await git(["-C", path, "checkout", "-b", branch]);
1640
+ if (checkout.error || (checkout.status ?? 1) !== 0) {
1641
+ const detail = spawnDetail(checkout);
1642
+ return {
1643
+ error: `existing worktree ${path} is clean but could not recover branch ${branch} from ${actualLabel}; remove/prune the stale worktree before retrying${detail ? `: ${detail}` : ""}`
1644
+ };
1645
+ }
1646
+ const recovered = await git(["-C", path, "branch", "--show-current"]);
1647
+ if (recovered.error || (recovered.status ?? 1) !== 0 || recovered.stdout.trim() !== branch) {
1648
+ const recoveredBranch = recovered.stdout.trim() || "unknown";
1649
+ const detail = spawnDetail(recovered);
1650
+ return {
1651
+ error: `existing worktree ${path} branch recovery ended on ${recoveredBranch}, expected ${branch}; remove/prune the stale worktree before retrying${detail ? `: ${detail}` : ""}`
1652
+ };
1653
+ }
1587
1654
  }
1588
1655
  return { cwd: worktree.cwd };
1589
1656
  }
@@ -1599,7 +1666,7 @@ async function ensureLocalWorktree(worktree, env) {
1599
1666
  const hasBranch = await git(["-C", repoRoot, "show-ref", "--verify", "--quiet", `refs/heads/${branch}`]);
1600
1667
  const add = hasBranch.status === 0 ? await git(["-C", repoRoot, "worktree", "add", path, branch]) : await git(["-C", repoRoot, "worktree", "add", "-b", branch, path, "HEAD"]);
1601
1668
  if (add.error || (add.status ?? 1) !== 0) {
1602
- const detail = (add.stderr || add.stdout || add.error || "").toString().trim();
1669
+ const detail = spawnDetail(add);
1603
1670
  return { error: `git worktree add failed for ${path}${detail ? `: ${detail}` : ""}` };
1604
1671
  }
1605
1672
  return { cwd: worktree.cwd };
@@ -1645,7 +1712,7 @@ function preflightRemoteSpec(spec, machine, metadata, opts) {
1645
1712
  throw new Error(`remote preflight failed on ${machine.id}: ${result.error.message}`);
1646
1713
  if ((result.status ?? 1) !== 0) {
1647
1714
  const detail = (result.stderr || result.stdout || `exit ${result.status ?? "unknown"}`).trim();
1648
- throw new Error(`remote preflight failed on ${machine.id}${detail ? `: ${detail}` : ""}`);
1715
+ throw new Error(remotePreflightFailureMessage(machine.id, detail));
1649
1716
  }
1650
1717
  }
1651
1718
  async function executeRemoteSpec(spec, machine, metadata, opts, fallbackSpec) {
@@ -57,6 +57,60 @@ export interface DeleteResponse {
57
57
  "ok": boolean;
58
58
  "deleted": boolean;
59
59
  }
60
+ export type RunReceiptMachine = string | Record<string, unknown>;
61
+ export interface RunReceiptSummary {
62
+ "text"?: string;
63
+ "stdout_bytes": number;
64
+ "stderr_bytes": number;
65
+ "stdout_excerpt"?: string;
66
+ "stderr_excerpt"?: string;
67
+ "error"?: string;
68
+ "duration_ms"?: number;
69
+ }
70
+ export interface RunReceipt {
71
+ "loop_id": string;
72
+ "run_id": string;
73
+ "machine": RunReceiptMachine;
74
+ "repo": string;
75
+ "task_ids": Array<string>;
76
+ "knowledge_ids": Array<string>;
77
+ "digest_id": string;
78
+ "started_at": string | null;
79
+ "finished_at": string | null;
80
+ "status": string;
81
+ "exit_code": number | null;
82
+ "summary": RunReceiptSummary;
83
+ "evidence_paths": Array<string>;
84
+ "created_at": string;
85
+ "updated_at": string;
86
+ }
87
+ export interface WriteRunReceiptInput {
88
+ "loop_id"?: string;
89
+ "run_id": string;
90
+ "machine"?: RunReceiptMachine;
91
+ "repo"?: string;
92
+ "task_ids"?: Array<string>;
93
+ "knowledge_ids"?: Array<string>;
94
+ "digest_id"?: string;
95
+ "started_at"?: string | null;
96
+ "finished_at"?: string | null;
97
+ "status"?: string;
98
+ "exit_code"?: number | null;
99
+ "summary"?: string | RunReceiptSummary | null;
100
+ "evidence_paths"?: Array<string>;
101
+ "stdout"?: string;
102
+ "stderr"?: string;
103
+ "error"?: string;
104
+ "duration_ms"?: number;
105
+ }
106
+ export interface RunReceiptResponse {
107
+ "ok": boolean;
108
+ "receipt": RunReceipt;
109
+ }
110
+ export interface RunReceiptListResponse {
111
+ "ok": boolean;
112
+ "receipts": Array<RunReceipt>;
113
+ }
60
114
  export interface LoopsClientOptions {
61
115
  /** Base URL, e.g. process.env.APP_API_URL. */
62
116
  baseUrl: string;
@@ -102,6 +156,19 @@ export declare class LoopsClient {
102
156
  archiveLoop(id: string, init?: RequestInit): Promise<LoopResponse>;
103
157
  /** Unarchive a loop */
104
158
  unarchiveLoop(id: string, init?: RequestInit): Promise<LoopResponse>;
159
+ /** List run receipts */
160
+ listRunReceipts(query?: {
161
+ "loopId"?: string;
162
+ "repo"?: string;
163
+ "taskId"?: string;
164
+ "knowledgeId"?: string;
165
+ "status"?: string;
166
+ "limit"?: number;
167
+ }, init?: RequestInit): Promise<RunReceiptListResponse>;
168
+ /** Write a run receipt */
169
+ writeRunReceipt(body: WriteRunReceiptInput, init?: RequestInit): Promise<RunReceiptResponse>;
170
+ /** Get a run receipt by run id */
171
+ getRunReceipt(runId: string, init?: RequestInit): Promise<RunReceiptResponse>;
105
172
  /** List runs */
106
173
  listRuns(query?: {
107
174
  "loopId"?: string;
package/dist/sdk/http.js CHANGED
@@ -117,6 +117,27 @@ class LoopsClient {
117
117
  init
118
118
  });
119
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
+ }
120
141
  async listRuns(query, init) {
121
142
  return this.request("GET", `/v1/runs`, {
122
143
  body: undefined,
@@ -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
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,21 +38,49 @@ export interface ListRunsFilters {
29
38
  status?: RunStatus;
30
39
  limit?: number;
31
40
  }
41
+ export interface ListRunReceiptsFilters {
42
+ loopId?: string;
43
+ repo?: string;
44
+ taskId?: string;
45
+ knowledgeId?: string;
46
+ status?: string;
47
+ limit?: number;
48
+ }
32
49
  export declare class LoopsClient {
33
- readonly store: Store;
50
+ /**
51
+ * The resolved data store: an on-box {@link LocalStore} (sqlite) or the hosted
52
+ * {@link ApiStore} (`/v1` + bearer key). EVERY data method routes through here,
53
+ * so nothing silently touches the on-box island while flipped to the cloud API.
54
+ */
55
+ readonly store: LoopStore;
34
56
  private readonly ownStore;
35
57
  private readonly runnerId;
36
58
  constructor(opts?: LoopsClientOptions);
37
- create(input: CreateLoopInput): Loop;
38
- list(filters?: ListLoopsFilters): Loop[];
39
- get(idOrName: string): Loop;
40
- pause(idOrName: string): Loop;
41
- resume(idOrName: string): Loop;
42
- stop(idOrName: string): Loop;
43
- archive(idOrName: string): Loop;
44
- unarchive(idOrName: string): Loop;
45
- delete(idOrName: string): boolean;
46
- runs(idOrName?: string, filters?: ListRunsFilters): LoopRun[];
59
+ /**
60
+ * The raw on-box sqlite {@link Store} for local-runtime operations that cannot
61
+ * route over HTTP — the scheduler (tick / inline run-now), migration
62
+ * export/import, and local diagnostics (doctor/health). These act on THIS
63
+ * machine's runtime and database, so they fail loudly instead of silently
64
+ * hitting the on-box island when the client is flipped to the hosted API.
65
+ */
66
+ private localRuntime;
67
+ create(input: CreateLoopInput): Promise<Loop>;
68
+ list(filters?: ListLoopsFilters): Promise<Loop[]>;
69
+ get(idOrName: string): Promise<Loop>;
70
+ pause(idOrName: string): Promise<Loop>;
71
+ resume(idOrName: string): Promise<Loop>;
72
+ stop(idOrName: string): Promise<Loop>;
73
+ archive(idOrName: string): Promise<Loop>;
74
+ unarchive(idOrName: string): Promise<Loop>;
75
+ delete(idOrName: string): Promise<boolean>;
76
+ runs(idOrName?: string, filters?: ListRunsFilters): Promise<LoopRun[]>;
77
+ writeReceipt(input: WriteRunReceiptInput): Promise<RunReceipt>;
78
+ receipt(runId: string): Promise<RunReceipt | undefined>;
79
+ receipts(filters?: ListRunReceiptsFilters): Promise<RunReceipt[]>;
80
+ goal(idOrName: string): Promise<{
81
+ goal?: Goal;
82
+ runs: GoalRun[];
83
+ }>;
47
84
  doctor(): DoctorReport;
48
85
  health(opts?: {
49
86
  includeArchived?: boolean;
@@ -54,10 +91,6 @@ export declare class LoopsClient {
54
91
  doctor?: boolean;
55
92
  daemon?: boolean;
56
93
  }): LoopsHealthScan;
57
- goal(idOrName: string): {
58
- goal?: Goal;
59
- runs: GoalRun[];
60
- };
61
94
  tick(): Promise<Awaited<ReturnType<typeof tick>>>;
62
95
  runNow(idOrName: string): Promise<LoopRun>;
63
96
  exportBundle(opts?: ExportLoopsMigrationOptions): LoopsMigrationBundle;
@@ -66,7 +99,7 @@ export declare class LoopsClient {
66
99
  planSelfHostedMigration(opts?: Omit<SelfHostedPlanOptions, "operation"> & {
67
100
  operation?: SelfHostedPlanOptions["operation"];
68
101
  }): Promise<LoopsMigrationPlan>;
69
- close(): void;
102
+ close(): Promise<void>;
70
103
  }
71
104
  export declare function loops(opts?: LoopsClientOptions): LoopsClient;
72
105
  export declare function openAutomationsRuntimeBinding(overrides?: Partial<OpenAutomationsRuntimeBinding>): OpenAutomationsRuntimeBinding;