@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.
- package/CHANGELOG.md +25 -0
- package/README.md +119 -29
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +825 -7
- package/dist/cli/index.js +5419 -2619
- package/dist/daemon/index.js +478 -84
- package/dist/generated/storage-kit/index.d.ts +1 -1
- package/dist/index.js +2521 -226
- package/dist/lib/cloud/mode.d.ts +17 -0
- package/dist/lib/cloud/resolve.d.ts +16 -0
- package/dist/lib/cloud/storage.d.ts +82 -0
- package/dist/lib/cloud/transport.d.ts +148 -0
- package/dist/lib/format.d.ts +2 -1
- package/dist/lib/migration.d.ts +40 -0
- package/dist/lib/mode.js +3 -3
- package/dist/lib/route/index.d.ts +2 -0
- package/dist/lib/route/policies.d.ts +51 -0
- package/dist/lib/route/provider-admission.d.ts +37 -0
- package/dist/lib/route/throttle.d.ts +4 -0
- package/dist/lib/route/types.d.ts +8 -0
- package/dist/lib/run-receipts.d.ts +14 -0
- package/dist/lib/storage/contract.d.ts +7 -1
- package/dist/lib/storage/index.js +710 -31
- package/dist/lib/storage/postgres-loop-storage.d.ts +6 -0
- package/dist/lib/storage/postgres-schema.js +29 -0
- package/dist/lib/storage/postgres.js +29 -0
- package/dist/lib/storage/sqlite.d.ts +6 -0
- package/dist/lib/storage/sqlite.js +412 -18
- package/dist/lib/store/index.d.ts +268 -0
- package/dist/lib/store.d.ts +48 -1
- package/dist/lib/store.js +396 -18
- package/dist/lib/template-kit.d.ts +25 -0
- package/dist/lib/templates.d.ts +16 -1
- package/dist/mcp/http.d.ts +16 -0
- package/dist/mcp/index.js +1658 -168
- package/dist/runner/index.js +76 -9
- package/dist/sdk/http.d.ts +67 -0
- package/dist/sdk/http.js +21 -0
- package/dist/sdk/index.d.ts +50 -17
- package/dist/sdk/index.js +1509 -132
- package/dist/serve/index.js +1598 -122
- package/dist/types.d.ts +49 -0
- package/docs/AUTOMATION_RUNTIME_DESIGN.md +442 -1
- package/docs/CUTOVER-RUNBOOK.md +73 -56
- package/docs/DEPLOYMENT_MODES.md +35 -18
- package/docs/RUNTIME_BOUNDARY.md +203 -0
- package/docs/USAGE.md +145 -27
- package/package.json +3 -3
package/dist/runner/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// package.json
|
|
4
4
|
var package_default = {
|
|
5
5
|
name: "@hasna/loops",
|
|
6
|
-
version: "0.4.
|
|
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
|
|
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
|
|
1586
|
-
return { error: `existing worktree ${path}
|
|
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
|
|
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(
|
|
1715
|
+
throw new Error(remotePreflightFailureMessage(machine.id, detail));
|
|
1649
1716
|
}
|
|
1650
1717
|
}
|
|
1651
1718
|
async function executeRemoteSpec(spec, machine, metadata, opts, fallbackSpec) {
|
package/dist/sdk/http.d.ts
CHANGED
|
@@ -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,
|
package/dist/sdk/index.d.ts
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
|
-
import type { CreateLoopInput, Goal, GoalRun, Loop, LoopRun, LoopStatus, OpenAutomationsRuntimeBinding, RunStatus } from "../types.js";
|
|
1
|
+
import type { CreateLoopInput, Goal, GoalRun, Loop, LoopRun, LoopStatus, OpenAutomationsRuntimeBinding, RunReceipt, RunStatus, WriteRunReceiptInput } from "../types.js";
|
|
2
2
|
import { type DoctorReport } from "../lib/doctor.js";
|
|
3
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
|
-
|
|
50
|
+
/**
|
|
51
|
+
* The resolved data store: an on-box {@link LocalStore} (sqlite) or the hosted
|
|
52
|
+
* {@link ApiStore} (`/v1` + bearer key). EVERY data method routes through here,
|
|
53
|
+
* so nothing silently touches the on-box island while flipped to the cloud API.
|
|
54
|
+
*/
|
|
55
|
+
readonly store: LoopStore;
|
|
34
56
|
private readonly ownStore;
|
|
35
57
|
private readonly runnerId;
|
|
36
58
|
constructor(opts?: LoopsClientOptions);
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
59
|
+
/**
|
|
60
|
+
* The raw on-box sqlite {@link Store} for local-runtime operations that cannot
|
|
61
|
+
* route over HTTP — the scheduler (tick / inline run-now), migration
|
|
62
|
+
* export/import, and local diagnostics (doctor/health). These act on THIS
|
|
63
|
+
* machine's runtime and database, so they fail loudly instead of silently
|
|
64
|
+
* hitting the on-box island when the client is flipped to the hosted API.
|
|
65
|
+
*/
|
|
66
|
+
private localRuntime;
|
|
67
|
+
create(input: CreateLoopInput): Promise<Loop>;
|
|
68
|
+
list(filters?: ListLoopsFilters): Promise<Loop[]>;
|
|
69
|
+
get(idOrName: string): Promise<Loop>;
|
|
70
|
+
pause(idOrName: string): Promise<Loop>;
|
|
71
|
+
resume(idOrName: string): Promise<Loop>;
|
|
72
|
+
stop(idOrName: string): Promise<Loop>;
|
|
73
|
+
archive(idOrName: string): Promise<Loop>;
|
|
74
|
+
unarchive(idOrName: string): Promise<Loop>;
|
|
75
|
+
delete(idOrName: string): Promise<boolean>;
|
|
76
|
+
runs(idOrName?: string, filters?: ListRunsFilters): Promise<LoopRun[]>;
|
|
77
|
+
writeReceipt(input: WriteRunReceiptInput): Promise<RunReceipt>;
|
|
78
|
+
receipt(runId: string): Promise<RunReceipt | undefined>;
|
|
79
|
+
receipts(filters?: ListRunReceiptsFilters): Promise<RunReceipt[]>;
|
|
80
|
+
goal(idOrName: string): Promise<{
|
|
81
|
+
goal?: Goal;
|
|
82
|
+
runs: GoalRun[];
|
|
83
|
+
}>;
|
|
47
84
|
doctor(): DoctorReport;
|
|
48
85
|
health(opts?: {
|
|
49
86
|
includeArchived?: boolean;
|
|
@@ -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;
|