@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.
- package/CHANGELOG.md +247 -0
- package/LICENSE +197 -13
- package/README.md +24 -56
- package/dist/cli/index.js +7474 -6295
- package/dist/daemon/control.d.ts +21 -1
- package/dist/daemon/daemon.d.ts +5 -0
- package/dist/daemon/index.js +2808 -1209
- package/dist/daemon/install.d.ts +1 -1
- package/dist/index.d.ts +21 -8
- package/dist/index.js +5305 -4293
- package/dist/lib/accounts.d.ts +6 -1
- package/dist/lib/agent-adapter.d.ts +74 -0
- package/dist/lib/backup.d.ts +25 -0
- package/dist/lib/errors.d.ts +21 -0
- package/dist/lib/executor.d.ts +10 -1
- package/dist/lib/goal/metadata.d.ts +16 -0
- package/dist/lib/goal/model-factory.d.ts +1 -0
- package/dist/lib/goal/prompts.d.ts +3 -1
- package/dist/lib/goal/types.d.ts +3 -0
- package/dist/lib/health.d.ts +1 -1
- package/dist/lib/ids.d.ts +8 -1
- package/dist/lib/machines.d.ts +6 -0
- package/dist/lib/process-identity.d.ts +16 -0
- package/dist/lib/{schedule.d.ts → recurrence.d.ts} +1 -0
- package/dist/lib/redact.d.ts +21 -0
- package/dist/lib/route/cursors.d.ts +18 -0
- package/dist/lib/route/drain.d.ts +6 -0
- package/dist/lib/route/fields.d.ts +27 -0
- package/dist/lib/route/gates.d.ts +26 -0
- package/dist/lib/route/index.d.ts +18 -0
- package/dist/lib/route/options.d.ts +31 -0
- package/dist/lib/route/parse.d.ts +8 -0
- package/dist/lib/route/pr-review.d.ts +11 -0
- package/dist/lib/route/provider.d.ts +41 -0
- package/dist/lib/route/route-event.d.ts +23 -0
- package/dist/lib/route/route-tasks.d.ts +75 -0
- package/dist/lib/route/throttle.d.ts +47 -0
- package/dist/lib/route/todos-cli.d.ts +21 -0
- package/dist/lib/route/types.d.ts +88 -0
- package/dist/lib/run-artifacts.d.ts +17 -2
- package/dist/lib/run-envelope.d.ts +41 -0
- package/dist/lib/scheduler.d.ts +78 -2
- package/dist/lib/store.d.ts +63 -0
- package/dist/lib/store.js +1007 -287
- package/dist/lib/template-kit.d.ts +70 -0
- package/dist/lib/templates-custom.d.ts +34 -0
- package/dist/lib/templates.d.ts +13 -81
- package/dist/lib/workflow-runner.d.ts +2 -0
- package/dist/mcp/index.d.ts +3 -0
- package/dist/mcp/index.js +3216 -1382
- package/dist/sdk/index.d.ts +29 -3
- package/dist/sdk/index.js +2740 -1041
- package/dist/test-helpers.d.ts +37 -0
- package/dist/types.d.ts +2 -0
- package/docs/USAGE.md +36 -14
- 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
|
|
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
|
-
}
|
|
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;
|
package/dist/lib/scheduler.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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;
|
package/dist/lib/store.d.ts
CHANGED
|
@@ -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;
|