@hasna/loops 0.3.60 → 0.4.1

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 (63) hide show
  1. package/CHANGELOG.md +265 -0
  2. package/LICENSE +197 -13
  3. package/README.md +95 -85
  4. package/dist/api/index.d.ts +11 -0
  5. package/dist/api/index.js +333 -0
  6. package/dist/cli/index.js +8013 -6659
  7. package/dist/daemon/control.d.ts +21 -1
  8. package/dist/daemon/daemon.d.ts +5 -0
  9. package/dist/daemon/index.js +2836 -1222
  10. package/dist/daemon/install.d.ts +1 -1
  11. package/dist/index.d.ts +23 -8
  12. package/dist/index.js +5715 -4541
  13. package/dist/lib/accounts.d.ts +6 -1
  14. package/dist/lib/agent-adapter.d.ts +74 -0
  15. package/dist/lib/backup.d.ts +25 -0
  16. package/dist/lib/errors.d.ts +21 -0
  17. package/dist/lib/executor.d.ts +10 -1
  18. package/dist/lib/goal/metadata.d.ts +16 -0
  19. package/dist/lib/goal/model-factory.d.ts +1 -0
  20. package/dist/lib/goal/prompts.d.ts +3 -1
  21. package/dist/lib/goal/types.d.ts +3 -0
  22. package/dist/lib/health.d.ts +1 -1
  23. package/dist/lib/ids.d.ts +8 -1
  24. package/dist/lib/machines.d.ts +6 -0
  25. package/dist/lib/mode.d.ts +48 -0
  26. package/dist/lib/mode.js +260 -0
  27. package/dist/lib/process-identity.d.ts +16 -0
  28. package/dist/lib/{schedule.d.ts → recurrence.d.ts} +1 -0
  29. package/dist/lib/redact.d.ts +21 -0
  30. package/dist/lib/route/cursors.d.ts +18 -0
  31. package/dist/lib/route/drain.d.ts +6 -0
  32. package/dist/lib/route/fields.d.ts +27 -0
  33. package/dist/lib/route/gates.d.ts +26 -0
  34. package/dist/lib/route/index.d.ts +18 -0
  35. package/dist/lib/route/options.d.ts +31 -0
  36. package/dist/lib/route/parse.d.ts +8 -0
  37. package/dist/lib/route/pr-review.d.ts +11 -0
  38. package/dist/lib/route/provider.d.ts +41 -0
  39. package/dist/lib/route/route-event.d.ts +23 -0
  40. package/dist/lib/route/route-tasks.d.ts +75 -0
  41. package/dist/lib/route/throttle.d.ts +47 -0
  42. package/dist/lib/route/todos-cli.d.ts +21 -0
  43. package/dist/lib/route/types.d.ts +88 -0
  44. package/dist/lib/run-artifacts.d.ts +17 -2
  45. package/dist/lib/run-envelope.d.ts +41 -0
  46. package/dist/lib/scheduler.d.ts +78 -2
  47. package/dist/lib/store.d.ts +63 -0
  48. package/dist/lib/store.js +1007 -287
  49. package/dist/lib/template-kit.d.ts +70 -0
  50. package/dist/lib/templates-custom.d.ts +34 -0
  51. package/dist/lib/templates.d.ts +13 -81
  52. package/dist/lib/workflow-runner.d.ts +2 -0
  53. package/dist/mcp/index.d.ts +3 -0
  54. package/dist/mcp/index.js +3231 -1382
  55. package/dist/runner/index.d.ts +9 -0
  56. package/dist/runner/index.js +295 -0
  57. package/dist/sdk/index.d.ts +29 -3
  58. package/dist/sdk/index.js +2743 -1044
  59. package/dist/test-helpers.d.ts +37 -0
  60. package/dist/types.d.ts +3 -1
  61. package/docs/DEPLOYMENT_MODES.md +140 -0
  62. package/docs/USAGE.md +102 -46
  63. package/package.json +23 -5
@@ -0,0 +1,27 @@
1
+ import type { EventEnvelope } from "@hasna/events";
2
+ /** Deep field extraction over Hasna event envelopes and todos task records. */
3
+ export declare function eventData(event: EventEnvelope): Record<string, unknown>;
4
+ export declare function eventMetadata(event: EventEnvelope): Record<string, unknown>;
5
+ export declare function stringField(value: unknown): string | undefined;
6
+ export declare function slugSegment(value: string, fallback?: string): string;
7
+ export declare function stableSuffix(value: string): string;
8
+ export declare function stableHash(parts: unknown[]): string;
9
+ export declare function taskEventField(data: Record<string, unknown>, keys: string[]): string | undefined;
10
+ export declare function objectField(value: unknown): Record<string, unknown> | undefined;
11
+ export declare function taskEventRecords(data: Record<string, unknown>, metadata: Record<string, unknown>): Record<string, unknown>[];
12
+ export declare function booleanLike(value: unknown): boolean;
13
+ export declare function hasTruthyField(records: Record<string, unknown>[], keys: string[]): boolean;
14
+ export declare function firstTruthyField(records: Record<string, unknown>[], keys: string[]): string | undefined;
15
+ export declare function automationRecords(data: Record<string, unknown>, metadata: Record<string, unknown>): Record<string, unknown>[];
16
+ export declare function tagsFromValue(value: unknown): string[];
17
+ export declare function taskEventTags(records: Record<string, unknown>[]): string[];
18
+ export declare function taskRouteEligibility(data: Record<string, unknown>, metadata: Record<string, unknown>): {
19
+ eligible: boolean;
20
+ reason?: string;
21
+ tags: string[];
22
+ };
23
+ export declare function canonicalRouteField(value: string): string;
24
+ export declare function recordFieldValues(record: Record<string, unknown>, field: string): string[];
25
+ export declare function routeFieldValues(records: Record<string, unknown>[], field: string): string[];
26
+ export declare function firstRouteField(records: Record<string, unknown>[], fields: string[]): string | undefined;
27
+ export declare function routeFieldList(records: Record<string, unknown>[], fields: string[]): string[] | undefined;
@@ -0,0 +1,26 @@
1
+ import type { CreateWorkflowInput, LoopTarget, WorkflowSpec } from "../../types.js";
2
+ import { CodedError } from "../errors.js";
3
+ import { preflightTarget } from "../executor.js";
4
+ import { preflightWorkflow } from "../workflow-runner.js";
5
+ /**
6
+ * Validation/preflight gates shared by CLI commands and route engines. Failures
7
+ * throw a `GateError` carrying the gate kind and structured context so the CLI
8
+ * error runner can render the stable `{ok:false, created:false, ...}` JSON shape.
9
+ */
10
+ export declare class GateError extends CodedError {
11
+ readonly gate: "validation" | "preflight";
12
+ readonly context: Record<string, unknown>;
13
+ constructor(gate: "validation" | "preflight", message: string, context: Record<string, unknown>);
14
+ }
15
+ export declare function normalizeLoopTargetForStorage(target: unknown, context: Record<string, unknown>, opts?: {
16
+ baseDir?: string;
17
+ }): Exclude<LoopTarget, {
18
+ type: "workflow";
19
+ }>;
20
+ export declare function normalizeWorkflowForStorage(body: CreateWorkflowInput, context: Record<string, unknown>): CreateWorkflowInput;
21
+ export declare function workflowBodyFromFile(file: string, fallbackName: string | undefined, context: Record<string, unknown>): CreateWorkflowInput;
22
+ export declare function preflightLoopTarget(target: Exclude<LoopTarget, {
23
+ type: "workflow";
24
+ }>, context: Record<string, unknown>, metadata: Parameters<typeof preflightTarget>[1], opts: Parameters<typeof preflightTarget>[2]): ReturnType<typeof preflightTarget>;
25
+ export declare function preflightStoredWorkflow(workflow: Parameters<typeof preflightWorkflow>[0], context: Record<string, unknown>, opts: Parameters<typeof preflightWorkflow>[1]): ReturnType<typeof preflightWorkflow>;
26
+ export declare function workflowSpecForPreflight(body: CreateWorkflowInput, id?: string): WorkflowSpec;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Route engine: turns Hasna events, todos ready queues, and health/hygiene
3
+ * findings into deduped, throttled OpenLoops workflow loops and todos tasks.
4
+ * Extracted from the CLI so commands stay thin and the SDK/daemon can reuse it.
5
+ */
6
+ export * from "./types.js";
7
+ export * from "./parse.js";
8
+ export { eventData, eventMetadata, objectField, slugSegment, stableHash, stableSuffix, stringField, tagsFromValue, taskEventField, taskEventTags, taskRouteEligibility, } from "./fields.js";
9
+ export { routeCursorKey, selectRouteItems, writeRouteCursor, writeRouteEvidence, type RouteSelection } from "./cursors.js";
10
+ export { GateError, normalizeLoopTargetForStorage, normalizeWorkflowForStorage, preflightLoopTarget, preflightStoredWorkflow, workflowBodyFromFile, workflowSpecForPreflight, } from "./gates.js";
11
+ export { accountPoolFromOpts, normalizeAgentProvider, permissionModeFromOpts, providerAuthProfileFromOpts, providerRoutingPublic, resolveProviderRouting, roleAccountFromOpts, sandboxFromOpts, SUPPORTED_AGENT_PROVIDERS, type ProviderRoutingDecision, } from "./provider.js";
12
+ export { prReviewRoutingDecision, type PrReviewRoutingDecision } from "./pr-review.js";
13
+ export { hasThrottleLimits, normalizeRoutePath, routeThrottleDecision, routeThrottleLimitsFromOpts, type RouteThrottleDecision, type RouteThrottleLimits, } from "./throttle.js";
14
+ export { generatedRouteSandboxPreflight, readEventEnvelopeInput, routeEventByKind, routeGenericEvent, routeTodosTaskEvent, todosTaskRouteTemplateId, } from "./route-event.js";
15
+ export { drainTodosTaskRoutes, type DrainResult } from "./drain.js";
16
+ export { buildHygieneRouteTasks, parseHygieneChecks, taskAutoRoute, upsertRouteTasks, type HygieneCheckKind, type HygieneRouteTask, type RouteTaskSpec, type UpsertRouteTasksOptions, } from "./route-tasks.js";
17
+ export { defaultLoopsProject, ensureTodosTaskList, runLocalCommand, runLocalCommandWithStdoutFile } from "./todos-cli.js";
18
+ export { addAgentRoutingOptions, addRouteEventOptions, addTodosDrainOptions, routeDrainArgs, type AgentRoutingOptionConfig } from "./options.js";
@@ -0,0 +1,31 @@
1
+ import type { Command } from "commander";
2
+ import type { TodosDrainOptions } from "./types.js";
3
+ export interface AgentRoutingOptionConfig {
4
+ /** Include event input options (--event-file/--event-json). */
5
+ eventInput?: boolean;
6
+ /** Include todos-task-only options (template, triage/planner roles, PR gating). */
7
+ todosTask?: boolean;
8
+ /** Default for --provider; when set the option description drops the codewith hint. */
9
+ providerDefault?: string;
10
+ /** Default for --name-prefix. */
11
+ namePrefixDefault?: string;
12
+ /** Override the --preflight help text. */
13
+ preflightDescription?: string;
14
+ /** Include --dry-run with this help text; omit the flag entirely when absent. */
15
+ dryRunDescription?: string;
16
+ }
17
+ /** Compose the shared agent-routing flag block onto a route command. */
18
+ export declare function addAgentRoutingOptions(command: Command, config?: AgentRoutingOptionConfig): Command;
19
+ /** Options for `routes create/preview <kind>` and the deprecated `events handle` aliases. */
20
+ export declare function addRouteEventOptions(command: Command, config?: AgentRoutingOptionConfig): Command;
21
+ /** Options for `routes drain/schedule todos-task` and the deprecated `events drain` alias. */
22
+ export declare function addTodosDrainOptions(command: Command, opts?: {
23
+ includeDryRun?: boolean;
24
+ preflightDescription?: string;
25
+ }): Command;
26
+ /**
27
+ * Rebuild the argv for a scheduled route drain loop from the same option specs
28
+ * used to declare the flags, so scheduled drains replay exactly the options the
29
+ * operator passed to `routes schedule`.
30
+ */
31
+ export declare function routeDrainArgs(opts: TodosDrainOptions): string[];
@@ -0,0 +1,8 @@
1
+ export declare function positiveInteger(raw: string | undefined, label: string): number | undefined;
2
+ export declare function nonNegativeInteger(raw: string | undefined, label: string): number | undefined;
3
+ export declare function positiveDuration(raw: string | undefined, label: string): number | undefined;
4
+ export declare function timeoutDuration(raw: string | undefined, label: string): number | null | undefined;
5
+ export declare function idleTimeoutDuration(raw: string | undefined, label: string): number | undefined;
6
+ export declare function splitList(value: string | undefined): string[] | undefined;
7
+ export declare function listFromRepeatedOpts(value: string[] | undefined): string[] | undefined;
8
+ export declare function collectValues(value: string, previous?: string[]): string[];
@@ -0,0 +1,11 @@
1
+ import type { TodosTaskRouteOptions } from "./types.js";
2
+ export interface PrReviewRoutingDecision {
3
+ required: boolean;
4
+ allowed: boolean;
5
+ reason?: string;
6
+ author?: string;
7
+ reviewers: string[];
8
+ selectedReviewer?: string;
9
+ signals: string[];
10
+ }
11
+ export declare function prReviewRoutingDecision(data: Record<string, unknown>, metadata: Record<string, unknown>, opts: TodosTaskRouteOptions): PrReviewRoutingDecision;
@@ -0,0 +1,41 @@
1
+ import type { AccountRef, AgentPermissionMode, AgentProvider, AgentSandbox } from "../../types.js";
2
+ import type { TodosTaskRouteOptions } from "./types.js";
3
+ export declare const SUPPORTED_AGENT_PROVIDERS: Set<AgentProvider>;
4
+ export type ProviderRoutingSource = "default" | "option" | "rule" | "metadata";
5
+ export interface ProviderRoutingRule {
6
+ raw: string;
7
+ field: string;
8
+ value: string;
9
+ provider: AgentProvider;
10
+ profiles?: string[];
11
+ }
12
+ export interface ProviderRoutingDecision {
13
+ provider: AgentProvider;
14
+ source: ProviderRoutingSource;
15
+ reason: string;
16
+ rule?: Pick<ProviderRoutingRule, "raw" | "field" | "value" | "provider" | "profiles">;
17
+ authProfile?: string;
18
+ authProfilePool?: string[];
19
+ account?: AccountRef;
20
+ accountPool?: AccountRef[];
21
+ }
22
+ export declare function normalizeAgentProvider(value: string | undefined, source: string): AgentProvider;
23
+ export declare function parseProviderRoutingRule(raw: string): ProviderRoutingRule;
24
+ export declare function accountPoolFromOpts(opts: {
25
+ accountPool?: string;
26
+ accountTool?: string;
27
+ }): AccountRef[] | undefined;
28
+ export declare function roleAccountFromOpts(opts: {
29
+ accountTool?: string;
30
+ }, profile: string | undefined): AccountRef | undefined;
31
+ export declare function providerRoutingPublic(decision: ProviderRoutingDecision): Record<string, unknown>;
32
+ export declare function resolveProviderRouting(data: Record<string, unknown>, metadata: Record<string, unknown>, opts: TodosTaskRouteOptions): ProviderRoutingDecision;
33
+ export declare function providerAuthProfileFromOpts(opts: {
34
+ authProfile?: string;
35
+ }, provider: AgentProvider): string | undefined;
36
+ export declare function sandboxFromOpts(opts: {
37
+ sandbox?: string;
38
+ }, provider: AgentProvider): AgentSandbox | undefined;
39
+ export declare function permissionModeFromOpts(opts: {
40
+ permissionMode?: string;
41
+ }, provider: AgentProvider): AgentPermissionMode | undefined;
@@ -0,0 +1,23 @@
1
+ import type { EventEnvelope } from "@hasna/events";
2
+ import type { AgentProvider, AgentSandbox, CreateWorkflowInput } from "../../types.js";
3
+ import type { TodosTaskRouteOptions, TodosTaskRoutePrint } from "./types.js";
4
+ /** Shared event-to-workflow route engine behind `routes create/preview` and the deprecated `events handle` aliases. */
5
+ interface RouteSandboxPreflight {
6
+ stepId: string;
7
+ provider: AgentProvider;
8
+ sandbox?: AgentSandbox;
9
+ worktreeEnabled?: boolean;
10
+ method: "provider-native-sandbox" | "isolated-worktree" | "manual-break-glass";
11
+ }
12
+ export declare function generatedRouteSandboxPreflight(workflow: CreateWorkflowInput): RouteSandboxPreflight[];
13
+ export declare function todosTaskRouteTemplateId(opts: {
14
+ template?: string;
15
+ }): string;
16
+ export declare function readEventEnvelopeInput(opts?: {
17
+ eventJson?: string;
18
+ eventFile?: string;
19
+ }): Promise<EventEnvelope>;
20
+ export declare function routeTodosTaskEvent(event: EventEnvelope, opts: TodosTaskRouteOptions): TodosTaskRoutePrint;
21
+ export declare function routeGenericEvent(event: EventEnvelope, opts: TodosTaskRouteOptions): TodosTaskRoutePrint;
22
+ export declare function routeEventByKind(kind: string, event: EventEnvelope, opts: TodosTaskRouteOptions): TodosTaskRoutePrint;
23
+ export {};
@@ -0,0 +1,75 @@
1
+ import type { Store } from "../store.js";
2
+ /** Deduped todos task upserts shared by `health route-tasks` and `hygiene route-tasks`. */
3
+ export interface TaskRouteOptions {
4
+ autoRoute?: boolean;
5
+ routeProjectPath?: string;
6
+ source: string;
7
+ }
8
+ export declare function taskAutoRoute(tags: string[], base: Record<string, unknown>, opts: TaskRouteOptions): {
9
+ tags: string[];
10
+ metadata: Record<string, unknown>;
11
+ autoRoute: {
12
+ requested: boolean;
13
+ enabled: boolean;
14
+ skippedReason?: string;
15
+ };
16
+ };
17
+ export interface RouteTaskSpec {
18
+ title: string;
19
+ description: string;
20
+ priority: string;
21
+ tags: string[];
22
+ fingerprint: string;
23
+ /** Base metadata handed to taskAutoRoute (before route flags are merged in). */
24
+ metadata: Record<string, unknown>;
25
+ /** Extra fields copied onto every action record for this task (e.g. hygiene `check`). */
26
+ extra?: Record<string, unknown>;
27
+ }
28
+ export interface UpsertRouteTasksOptions {
29
+ project: string;
30
+ taskList: {
31
+ slug: string;
32
+ name: string;
33
+ description: string;
34
+ };
35
+ cursorKey: string;
36
+ maxActions: number;
37
+ dryRun?: boolean;
38
+ autoRoute?: boolean;
39
+ routeProjectPath?: string;
40
+ source: string;
41
+ evidence: {
42
+ kind: string;
43
+ dir?: string;
44
+ };
45
+ /** Command-specific summary fields merged into the output before routing/actions. */
46
+ summary: Record<string, unknown>;
47
+ tasks: RouteTaskSpec[];
48
+ }
49
+ export interface UpsertRouteTasksResult {
50
+ ok: boolean;
51
+ output: Record<string, unknown>;
52
+ evidencePath?: string;
53
+ }
54
+ export declare function upsertRouteTasks(opts: UpsertRouteTasksOptions): UpsertRouteTasksResult;
55
+ export type HygieneCheckKind = "names" | "duplicates" | "scripts";
56
+ export interface HygieneRouteTask {
57
+ check: HygieneCheckKind;
58
+ title: string;
59
+ description: string;
60
+ priority: "critical" | "high" | "medium" | "low";
61
+ tags: string[];
62
+ fingerprint: string;
63
+ metadata: Record<string, unknown>;
64
+ }
65
+ export declare function parseHygieneChecks(value: string | undefined): HygieneCheckKind[];
66
+ export declare function buildHygieneRouteTasks(store: Store, opts: {
67
+ checks: HygieneCheckKind[];
68
+ includeInactive?: boolean;
69
+ limit?: number;
70
+ scriptsDir?: string;
71
+ }): {
72
+ checked: Record<HygieneCheckKind, number>;
73
+ findings: number;
74
+ tasks: HygieneRouteTask[];
75
+ };
@@ -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;