@hasna/loops 0.3.60 → 0.4.0

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 (56) hide show
  1. package/CHANGELOG.md +247 -0
  2. package/LICENSE +197 -13
  3. package/README.md +24 -56
  4. package/dist/cli/index.js +7474 -6295
  5. package/dist/daemon/control.d.ts +21 -1
  6. package/dist/daemon/daemon.d.ts +5 -0
  7. package/dist/daemon/index.js +2808 -1209
  8. package/dist/daemon/install.d.ts +1 -1
  9. package/dist/index.d.ts +21 -8
  10. package/dist/index.js +5305 -4293
  11. package/dist/lib/accounts.d.ts +6 -1
  12. package/dist/lib/agent-adapter.d.ts +74 -0
  13. package/dist/lib/backup.d.ts +25 -0
  14. package/dist/lib/errors.d.ts +21 -0
  15. package/dist/lib/executor.d.ts +10 -1
  16. package/dist/lib/goal/metadata.d.ts +16 -0
  17. package/dist/lib/goal/model-factory.d.ts +1 -0
  18. package/dist/lib/goal/prompts.d.ts +3 -1
  19. package/dist/lib/goal/types.d.ts +3 -0
  20. package/dist/lib/health.d.ts +1 -1
  21. package/dist/lib/ids.d.ts +8 -1
  22. package/dist/lib/machines.d.ts +6 -0
  23. package/dist/lib/process-identity.d.ts +16 -0
  24. package/dist/lib/{schedule.d.ts → recurrence.d.ts} +1 -0
  25. package/dist/lib/redact.d.ts +21 -0
  26. package/dist/lib/route/cursors.d.ts +18 -0
  27. package/dist/lib/route/drain.d.ts +6 -0
  28. package/dist/lib/route/fields.d.ts +27 -0
  29. package/dist/lib/route/gates.d.ts +26 -0
  30. package/dist/lib/route/index.d.ts +18 -0
  31. package/dist/lib/route/options.d.ts +31 -0
  32. package/dist/lib/route/parse.d.ts +8 -0
  33. package/dist/lib/route/pr-review.d.ts +11 -0
  34. package/dist/lib/route/provider.d.ts +41 -0
  35. package/dist/lib/route/route-event.d.ts +23 -0
  36. package/dist/lib/route/route-tasks.d.ts +75 -0
  37. package/dist/lib/route/throttle.d.ts +47 -0
  38. package/dist/lib/route/todos-cli.d.ts +21 -0
  39. package/dist/lib/route/types.d.ts +88 -0
  40. package/dist/lib/run-artifacts.d.ts +17 -2
  41. package/dist/lib/run-envelope.d.ts +41 -0
  42. package/dist/lib/scheduler.d.ts +78 -2
  43. package/dist/lib/store.d.ts +63 -0
  44. package/dist/lib/store.js +1007 -287
  45. package/dist/lib/template-kit.d.ts +70 -0
  46. package/dist/lib/templates-custom.d.ts +34 -0
  47. package/dist/lib/templates.d.ts +13 -81
  48. package/dist/lib/workflow-runner.d.ts +2 -0
  49. package/dist/mcp/index.d.ts +3 -0
  50. package/dist/mcp/index.js +3216 -1382
  51. package/dist/sdk/index.d.ts +29 -3
  52. package/dist/sdk/index.js +2740 -1041
  53. package/dist/test-helpers.d.ts +37 -0
  54. package/dist/types.d.ts +2 -0
  55. package/docs/USAGE.md +36 -14
  56. package/package.json +6 -3
@@ -0,0 +1,47 @@
1
+ import type { Store } from "../store.js";
2
+ /** Active-workflow admission throttles and canonical project-path handling. */
3
+ export interface RouteThrottleLimits {
4
+ maxActive?: number;
5
+ maxActivePerProject?: number;
6
+ maxActivePerProjectGroup?: number;
7
+ }
8
+ export interface RouteThrottleDecision {
9
+ allowed: boolean;
10
+ reason?: string;
11
+ projectPath: string;
12
+ projectGroup?: string;
13
+ limits: RouteThrottleLimits;
14
+ counts: {
15
+ global: number;
16
+ project: number;
17
+ projectGroup?: number;
18
+ };
19
+ }
20
+ export declare function routeThrottleLimitsFromOpts(opts: {
21
+ maxActive?: string;
22
+ maxActivePerProject?: string;
23
+ maxActivePerProjectGroup?: string;
24
+ }): RouteThrottleLimits;
25
+ export declare function hasThrottleLimits(limits: RouteThrottleLimits): boolean;
26
+ export declare function normalizeRoutePath(value: string | undefined): string | undefined;
27
+ export declare function routeProjectGroup(optsGroup: string | undefined, data: Record<string, unknown>, metadata: Record<string, unknown>): string | undefined;
28
+ export declare function routeThrottleDecision(store: Store, args: {
29
+ projectPath: string;
30
+ projectGroup?: string;
31
+ limits: RouteThrottleLimits;
32
+ }): RouteThrottleDecision;
33
+ export declare function routeThrottleDryRunPreview(args: {
34
+ projectPath: string;
35
+ projectGroup?: string;
36
+ limits: RouteThrottleLimits;
37
+ }): {
38
+ limits: RouteThrottleLimits;
39
+ projectGroup?: string | undefined;
40
+ evaluated: boolean;
41
+ reason: string;
42
+ projectPath: string;
43
+ };
44
+ export declare function isExistingGitProjectPath(path: string): boolean;
45
+ export declare function validateRequiredRouteWorktreeProjectPath(opts: {
46
+ worktreeMode?: string;
47
+ }, projectPath: string): void;
@@ -0,0 +1,21 @@
1
+ /** Local `todos` CLI transport used by route drains and route-tasks commands. */
2
+ export declare function defaultLoopsProject(): string;
3
+ export interface LocalCommandResult {
4
+ ok: boolean;
5
+ status: number | null;
6
+ stdout: string;
7
+ stderr: string;
8
+ error: string;
9
+ }
10
+ export declare function runLocalCommand(command: string, args: string[], opts?: {
11
+ input?: string;
12
+ timeoutMs?: number;
13
+ maxBuffer?: number;
14
+ }): LocalCommandResult;
15
+ export declare function runLocalCommandWithStdoutFile(command: string, args: string[], opts?: {
16
+ input?: string;
17
+ timeoutMs?: number;
18
+ maxBuffer?: number;
19
+ }): LocalCommandResult;
20
+ export declare function ensureTodosTaskList(project: string, slug: string, name: string, description: string): string;
21
+ export declare function todosMutationSummary(result: LocalCommandResult): Record<string, unknown>;
@@ -0,0 +1,88 @@
1
+ /** Typed option bags shared by the route engine, drain engine, and CLI commands. */
2
+ export interface TodosTaskRouteOptions {
3
+ eventFile?: string;
4
+ eventJson?: string;
5
+ template?: string;
6
+ provider?: string;
7
+ providerRule?: string[];
8
+ authProfile?: string;
9
+ authProfilePool?: string;
10
+ triageAuthProfile?: string;
11
+ plannerAuthProfile?: string;
12
+ workerAuthProfile?: string;
13
+ verifierAuthProfile?: string;
14
+ account?: string;
15
+ accountPool?: string;
16
+ triageAccount?: string;
17
+ plannerAccount?: string;
18
+ workerAccount?: string;
19
+ verifierAccount?: string;
20
+ accountTool?: string;
21
+ model?: string;
22
+ variant?: string;
23
+ agent?: string;
24
+ addDir?: string[];
25
+ timeout?: string;
26
+ verifierIdleTimeout?: string;
27
+ permissionMode?: string;
28
+ sandbox?: string;
29
+ manualBreakGlass?: boolean;
30
+ projectPath?: string;
31
+ projectGroup?: string;
32
+ maxActive?: string;
33
+ maxActivePerProject?: string;
34
+ maxActivePerProjectGroup?: string;
35
+ worktreeMode?: string;
36
+ worktreeRoot?: string;
37
+ worktreeBranchPrefix?: string;
38
+ prHandoff?: boolean;
39
+ githubReviewer?: string;
40
+ githubReviewerPool?: string;
41
+ namePrefix?: string;
42
+ preflight?: boolean;
43
+ dryRun?: boolean;
44
+ todosProject?: string;
45
+ }
46
+ export interface TodosReadyTask {
47
+ id?: string;
48
+ task_id?: string;
49
+ taskId?: string;
50
+ title?: string;
51
+ description?: string;
52
+ body?: string;
53
+ status?: string;
54
+ working_dir?: string;
55
+ workingDir?: string;
56
+ project_path?: string;
57
+ projectPath?: string;
58
+ cwd?: string;
59
+ tags?: string[] | string;
60
+ metadata?: Record<string, unknown>;
61
+ project_id?: string;
62
+ projectId?: string;
63
+ task_list_id?: string;
64
+ taskListId?: string;
65
+ task_list?: {
66
+ id?: string;
67
+ slug?: string;
68
+ };
69
+ [key: string]: unknown;
70
+ }
71
+ export interface TodosDrainOptions extends TodosTaskRouteOptions {
72
+ todosProject?: string;
73
+ todosProjectId?: string;
74
+ taskList?: string;
75
+ tags?: string;
76
+ tag?: string;
77
+ projectPathPrefix?: string;
78
+ limit?: string;
79
+ scanLimit?: string;
80
+ maxDispatch?: string;
81
+ evidenceDir?: string;
82
+ compact?: boolean;
83
+ }
84
+ export interface TodosTaskRoutePrint {
85
+ kind: "skipped" | "deduped" | "throttled" | "created";
86
+ value: Record<string, unknown>;
87
+ human: string;
88
+ }
@@ -1,7 +1,7 @@
1
1
  export declare function safeRunPathSlug(value: string | undefined, fallback: string): string;
2
2
  export declare function workflowRunSubjectKey(kind: string | undefined, rawSubjectRef: string | undefined): string;
3
3
  export declare function workflowRunProjectSlug(projectKey: string | undefined): string;
4
- export declare function writeWorkflowRunManifest(args: {
4
+ export interface WorkflowRunManifestArgs {
5
5
  loopsDataDir: string;
6
6
  workflowRunId: string;
7
7
  workflowId: string;
@@ -12,4 +12,19 @@ export declare function writeWorkflowRunManifest(args: {
12
12
  subjectKind?: string;
13
13
  rawSubjectRef?: string;
14
14
  payload: Record<string, unknown>;
15
- }): string;
15
+ }
16
+ export interface StagedWorkflowRunManifest {
17
+ /** Final manifest.json location, valid only after {@link commitWorkflowRunManifest}. */
18
+ manifestPath: string;
19
+ /** Temp file holding the staged manifest content. */
20
+ tmpPath: string;
21
+ }
22
+ /**
23
+ * Write the manifest to `manifest.json.tmp` so callers can stage the
24
+ * filesystem side effect before opening a database transaction and promote it
25
+ * with {@link commitWorkflowRunManifest} only after COMMIT succeeds.
26
+ */
27
+ export declare function stageWorkflowRunManifest(args: WorkflowRunManifestArgs): StagedWorkflowRunManifest;
28
+ export declare function commitWorkflowRunManifest(staged: StagedWorkflowRunManifest): string;
29
+ export declare function discardWorkflowRunManifest(staged: StagedWorkflowRunManifest): void;
30
+ export declare function writeWorkflowRunManifest(args: WorkflowRunManifestArgs): string;
@@ -0,0 +1,41 @@
1
+ import type { ExecutorResult, WorkflowRun, WorkflowStepRun, WorkflowStepRunStatus } from "../types.js";
2
+ /** Maximum characters kept from each end of a child output when building parent envelopes. */
3
+ export declare const ENVELOPE_EXCERPT_CHARS = 2048;
4
+ /** Stable marker prefix recorded on skipped step runs that were blocked by policy exit codes. */
5
+ export declare const BLOCKED_STEP_ERROR_PREFIX = "blocked:";
6
+ /**
7
+ * Excerpts scrub BEFORE truncating: cutting raw output at fixed character
8
+ * boundaries can bisect a credential (e.g. an `sk-ant-…` key whose prefix
9
+ * falls in the omitted middle), leaving a fragment no scrub pattern matches.
10
+ * scrubSecrets is idempotent, so store-time re-scrubbing stays safe.
11
+ */
12
+ export declare function boundedExcerpt(text: string | undefined, limit?: number): string | undefined;
13
+ export interface OutputSummary {
14
+ stdoutBytes: number;
15
+ stderrBytes: number;
16
+ stdoutExcerpt?: string;
17
+ stderrExcerpt?: string;
18
+ }
19
+ export declare function summarizeOutput(stdout: string | undefined, stderr: string | undefined): OutputSummary;
20
+ export interface ExecutorResultSummary extends OutputSummary {
21
+ status: ExecutorResult["status"];
22
+ exitCode?: number;
23
+ durationMs: number;
24
+ error?: string;
25
+ }
26
+ export declare function summarizeExecutorResult(result: Pick<ExecutorResult, "status" | "exitCode" | "durationMs" | "stdout" | "stderr" | "error">): ExecutorResultSummary;
27
+ export interface WorkflowStepRunSummary extends OutputSummary {
28
+ stepId: string;
29
+ status: WorkflowStepRunStatus;
30
+ exitCode?: number;
31
+ durationMs?: number;
32
+ error?: string;
33
+ blocked: boolean;
34
+ }
35
+ export declare function isBlockedStepRun(step: Pick<WorkflowStepRun, "status" | "error"> | undefined): boolean;
36
+ export declare function summarizeWorkflowStepRun(step: WorkflowStepRun): WorkflowStepRunSummary;
37
+ /**
38
+ * Parent-run result envelope: per-step summaries only. Full child output stays on the
39
+ * workflow step run rows; duplicating it here bloated the database and propagated secrets.
40
+ */
41
+ export declare function workflowRunEnvelope(run: WorkflowRun, steps: WorkflowStepRun[]): string;
@@ -10,6 +10,10 @@ export interface SchedulerDeps {
10
10
  execute?: (loop: Loop, run: LoopRun) => Promise<ExecutorResult>;
11
11
  onError?: (loop: Loop, error: unknown) => void;
12
12
  onRun?: (run: LoopRun) => void;
13
+ /** randomness source for retry jitter; injectable for deterministic tests */
14
+ random?: () => number;
15
+ /** consecutive-final-failure count that trips the circuit breaker; number, per-loop resolver, or <= 0 to disable */
16
+ circuitBreakerThreshold?: number | ((loop: Loop) => number | undefined);
13
17
  }
14
18
  export interface TickResult {
15
19
  claimed: LoopRun[];
@@ -29,9 +33,81 @@ export declare function manualRunScheduledFor(loop: Loop, now?: Date): string;
29
33
  export declare function shouldAdvanceManualRun(loop: Loop, scheduledFor: string, now?: Date): boolean;
30
34
  export type ManualRunSource = "ad_hoc" | "due_slot" | "retry_slot";
31
35
  export declare function manualRunSource(loop: Loop, scheduledFor: string, now?: Date): ManualRunSource;
32
- export declare function advanceLoop(store: Store, loop: Loop, run: LoopRun, finishedAt: Date, succeeded: boolean, opts?: {
36
+ /**
37
+ * Inline (non-daemon) runners claim runs as `<surface>:<pid>`: `manual:<pid>`
38
+ * (CLI run-now), `manual-tick:<pid>` (CLI tick), `sdk:<pid>` (LoopsClient
39
+ * default), and caller-supplied SDK runner ids following the same convention.
40
+ * The daemon claims as `${hostname}:${pid}:${leaseId}` (three segments) and
41
+ * MCP run-now uses schedule mode, so neither ever matches. Centralized here so
42
+ * the daemon's "spare runs owned by a live inline runner" check cannot drift
43
+ * from the surfaces that share runLoopNow/executeClaimedRun semantics.
44
+ */
45
+ export declare const INLINE_RUNNER_ID_PATTERN: RegExp;
46
+ /** Owner pid of an inline runner claim, or undefined for daemon/unknown claims. */
47
+ export declare function inlineRunnerOwnerPid(claimedBy: string | undefined): number | undefined;
48
+ export type RunLoopNowMode = "inline" | "schedule";
49
+ export interface RunLoopNowDeps {
50
+ store: Store;
51
+ /** loop id or exact loop name */
52
+ idOrName: string;
53
+ runnerId: string;
54
+ /**
55
+ * "inline" claims and executes the run in this process (CLI/SDK semantics);
56
+ * "schedule" only marks the loop due now for daemon pickup (MCP semantics).
57
+ */
58
+ mode?: RunLoopNowMode;
59
+ now?: () => Date;
60
+ execute?: (loop: Loop, run: LoopRun) => Promise<ExecutorResult>;
61
+ }
62
+ export interface RunLoopNowScheduled {
63
+ mode: "schedule";
64
+ loop: Loop;
65
+ scheduledFor: string;
66
+ }
67
+ export interface RunLoopNowExecuted {
68
+ mode: "inline";
69
+ loop: Loop;
70
+ run: LoopRun;
71
+ source: ManualRunSource;
72
+ advancedLoop: boolean;
73
+ }
74
+ export type RunLoopNowResult = RunLoopNowScheduled | RunLoopNowExecuted;
75
+ export declare function runLoopNow(deps: RunLoopNowDeps & {
76
+ mode: "schedule";
77
+ }): Promise<RunLoopNowScheduled>;
78
+ export declare function runLoopNow(deps: RunLoopNowDeps & {
79
+ mode?: "inline";
80
+ }): Promise<RunLoopNowExecuted>;
81
+ export declare const MAX_RETRY_DELAY_MS: number;
82
+ export declare const DEFAULT_CIRCUIT_BREAKER_THRESHOLD = 5;
83
+ export declare const CIRCUIT_BREAKER_REASON_PREFIX = "circuit breaker open";
84
+ export declare const MAX_SKIPS_PER_LOOP_PER_TICK = 10;
85
+ /**
86
+ * Exponential retry backoff with jitter:
87
+ * delay = retryDelayMs * 2^(attempt-1) * (0.5 + random), capped at 6h.
88
+ * Failures classified as rate_limit/auth back off 4x harder, since hammering
89
+ * a throttled or misconfigured provider only makes things worse.
90
+ */
91
+ export declare function retryBackoffDelayMs(loop: Loop, run: LoopRun, random?: () => number): number;
92
+ export interface AdvanceLoopOptions {
33
93
  daemonLeaseId?: string;
34
- }): void;
94
+ random?: () => number;
95
+ circuitBreakerThreshold?: number | ((loop: Loop) => number | undefined);
96
+ onRun?: (run: LoopRun) => void;
97
+ }
98
+ /**
99
+ * Count consecutive final failures (failed/timed_out/abandoned) in recent run
100
+ * history. Skipped bookkeeping runs and in-flight runs are neutral; a success
101
+ * resets the streak. Failed runs with attempts remaining (attempt <
102
+ * maxAttempts) are pending retries by the scheduler's own semantics, so they
103
+ * are neutral too — only exhausted slots count as final failures. A previous
104
+ * circuit-breaker marker acts as a watermark (compared via scheduledFor, which
105
+ * shares the scheduler clock with run slots, unlike the marker's wall-clock
106
+ * createdAt): only failures after it count, so a manual resume requires a
107
+ * fresh streak before the breaker can trip again.
108
+ */
109
+ export declare function consecutiveFailureCount(store: Store, loopId: string, maxAttempts?: number, scanLimit?: number): number;
110
+ export declare function advanceLoop(store: Store, loop: Loop, run: LoopRun, finishedAt: Date, succeeded: boolean, opts?: AdvanceLoopOptions): void;
35
111
  export declare function executeClaimedRun(deps: {
36
112
  store: Store;
37
113
  runnerId: string;
@@ -46,6 +46,35 @@ export interface CreateGoalPlanNodeInput {
46
46
  priority?: number;
47
47
  tokenBudget?: number;
48
48
  }
49
+ export interface RecordRunProcessInput {
50
+ pid: number;
51
+ pgid?: number;
52
+ processStartedAt?: string;
53
+ }
54
+ export interface RecoverExpiredRunLeasesResult {
55
+ /** Runs whose lease expired with no live process; marked abandoned. */
56
+ abandoned: LoopRun[];
57
+ /** Runs whose lease expired while their process (group) is still alive; lease deferred. */
58
+ deferred: LoopRun[];
59
+ }
60
+ export interface PruneHistoryOptions {
61
+ /** Delete terminal runs whose created_at is older than this many days. */
62
+ maxAgeDays?: number;
63
+ /** Always retain at least this many of the most recent runs per loop. */
64
+ keepPerLoop?: number;
65
+ /** Report what would be deleted without deleting anything. */
66
+ dryRun?: boolean;
67
+ /** Injectable clock for tests. */
68
+ now?: Date;
69
+ }
70
+ export interface PruneHistorySummary {
71
+ dryRun: boolean;
72
+ cutoff?: string;
73
+ keepPerLoop?: number;
74
+ loopRuns: number;
75
+ workflowRuns: number;
76
+ goalRuns: number;
77
+ }
49
78
  export interface RecordGoalEventInput {
50
79
  goalId: string;
51
80
  turn?: number;
@@ -61,6 +90,8 @@ export declare class Store {
61
90
  private rootDir;
62
91
  constructor(path?: string);
63
92
  private migrate;
93
+ private migrations;
94
+ private createBaseSchema;
64
95
  /**
65
96
  * Add a column only if it does not already exist. Idempotent — avoids the
66
97
  * "duplicate column name" error that SQLite logs (via libsqlite3, before any
@@ -70,6 +101,8 @@ export declare class Store {
70
101
  */
71
102
  private addColumnIfMissing;
72
103
  private createWorkflowRunBackfillIndexes;
104
+ /** Run `fn` inside a write transaction unless the caller already opened one. */
105
+ private transact;
73
106
  private assertDaemonLeaseFence;
74
107
  private assertNoNestedWorkflowGoal;
75
108
  createLoop(input: CreateLoopInput, from?: Date): Loop;
@@ -179,6 +212,13 @@ export declare class Store {
179
212
  limit?: number;
180
213
  }): Goal[];
181
214
  createGoalPlanNodes(goalId: string, nodes: CreateGoalPlanNodeInput[], opts?: DaemonLeaseFence): GoalPlanNode[];
215
+ /**
216
+ * Insert a plan node, detecting (rather than silently ignoring) conflicts:
217
+ * an existing (plan_id, key) row means the node is already planned and is
218
+ * kept; a primary-key collision retries with a fresh id instead of dropping
219
+ * the node on the floor.
220
+ */
221
+ private insertGoalPlanNode;
182
222
  listGoalPlanNodes(goalIdOrPlanId: string): GoalPlanNode[];
183
223
  updateGoalStatus(goalId: string, status: GoalStatus, opts?: DaemonLeaseFence): Goal;
184
224
  addGoalUsage(goalId: string, tokens: number, timeUsedSeconds?: number, opts?: DaemonLeaseFence): Goal;
@@ -215,6 +255,11 @@ export declare class Store {
215
255
  hasRunningRun(loopId: string): boolean;
216
256
  hasRunningRunForSlot(loopId: string, scheduledFor: string): boolean;
217
257
  markRunPid(id: string, pid: number, claimedBy?: string, opts?: DaemonLeaseFence): LoopRun | undefined;
258
+ /**
259
+ * Record the spawned child's process identity (pid, process group id, start
260
+ * time) so recovery can signal the whole process group later.
261
+ */
262
+ recordRunProcess(runId: string, info: RecordRunProcessInput, opts?: DaemonLeaseFence): LoopRun | undefined;
218
263
  private hasLiveWorkflowStepProcesses;
219
264
  createSkippedRun(loop: Loop, scheduledFor: string, reason: string, opts?: DaemonLeaseFence): LoopRun;
220
265
  getRun(id: string): LoopRun | undefined;
@@ -237,12 +282,30 @@ export declare class Store {
237
282
  limit?: number;
238
283
  scanLimit?: number;
239
284
  }): LoopRun[];
285
+ /**
286
+ * Recover expired run leases and report both outcomes: runs abandoned (no
287
+ * live process) and runs deferred because their process (group) is still
288
+ * alive. Entries carry pid/pgid/processStartedAt so the daemon can signal
289
+ * orphaned process groups (SIGTERM then SIGKILL) after recovery.
290
+ */
291
+ recoverExpiredRunLeasesDetailed(now?: Date, opts?: DaemonLeaseFence & {
292
+ limit?: number;
293
+ scanLimit?: number;
294
+ }): RecoverExpiredRunLeasesResult;
240
295
  expireLoops(now?: Date, opts?: DaemonLeaseFence): Loop[];
241
296
  countLoops(status?: LoopStatus, opts?: {
242
297
  archived?: boolean;
243
298
  includeArchived?: boolean;
244
299
  }): number;
245
300
  countRuns(status?: RunStatus): number;
301
+ /**
302
+ * Delete old terminal run history: loop runs plus their attached workflow
303
+ * runs (step runs and events cascade), goal run events, and per-run manifest
304
+ * directories. At least one of maxAgeDays / keepPerLoop must be provided;
305
+ * when both are given a run is only deleted when it is older than the cutoff
306
+ * AND beyond the per-loop retention floor. Running runs are never touched.
307
+ */
308
+ pruneHistory(opts: PruneHistoryOptions): PruneHistorySummary;
246
309
  acquireDaemonLease(input: {
247
310
  id: string;
248
311
  pid: number;