@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
package/dist/lib/accounts.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { AccountRef, AgentProvider } from "../types.js";
|
|
2
2
|
export declare function accountToolForProvider(provider: AgentProvider): string;
|
|
3
3
|
export declare function parseAccountExportLines(output: string): Record<string, string>;
|
|
4
|
-
export declare function resolveAccountEnv(account: AccountRef | undefined, toolHint?: string, env?: NodeJS.ProcessEnv): Record<string, string
|
|
4
|
+
export declare function resolveAccountEnv(account: AccountRef | undefined, toolHint?: string, env?: NodeJS.ProcessEnv): Promise<Record<string, string>>;
|
|
5
|
+
/**
|
|
6
|
+
* Synchronous variant reserved for the synchronous preflight path; execution
|
|
7
|
+
* paths must use the async resolveAccountEnv so the daemon event loop never blocks.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveAccountEnvSync(account: AccountRef | undefined, toolHint?: string, env?: NodeJS.ProcessEnv): Record<string, string>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { AgentProvider, AgentSandbox, AgentTarget } from "../types.js";
|
|
2
|
+
export type ProviderPromptChannel = "stdin" | "argv";
|
|
3
|
+
export interface ProviderCapabilities {
|
|
4
|
+
/** Sandbox values the provider CLI accepts; empty when sandboxing is unsupported. */
|
|
5
|
+
sandbox: readonly AgentSandbox[];
|
|
6
|
+
/** Whether the provider runs as a durable background agent instead of a one-shot process. */
|
|
7
|
+
durable: boolean;
|
|
8
|
+
/** Whether the provider can be dispatched to a remote machine transport. */
|
|
9
|
+
remote: boolean;
|
|
10
|
+
/** How the prompt reaches the provider process. */
|
|
11
|
+
promptChannel: ProviderPromptChannel;
|
|
12
|
+
}
|
|
13
|
+
export interface AgentInvocation {
|
|
14
|
+
command: string;
|
|
15
|
+
args: string[];
|
|
16
|
+
/** Prompt delivered on stdin; unset only for argv-channel providers. */
|
|
17
|
+
stdin?: string;
|
|
18
|
+
/** Executables of which at least one must exist besides the command itself. */
|
|
19
|
+
preflightAnyOf?: string[];
|
|
20
|
+
}
|
|
21
|
+
export interface ProviderAdapter {
|
|
22
|
+
provider: AgentProvider;
|
|
23
|
+
capabilities: ProviderCapabilities;
|
|
24
|
+
validate(target: AgentTarget, label?: string): void;
|
|
25
|
+
buildInvocation(target: AgentTarget): AgentInvocation;
|
|
26
|
+
}
|
|
27
|
+
export declare const UNSAFE_CODEWITH_DURABLE_EXTRA_ARGS: Set<string>;
|
|
28
|
+
export declare const PROVIDER_ADAPTERS: Record<AgentProvider, ProviderAdapter>;
|
|
29
|
+
export declare const AGENT_PROVIDERS: AgentProvider[];
|
|
30
|
+
export declare function providerAdapter(provider: AgentProvider): ProviderAdapter;
|
|
31
|
+
export interface SpawnCaptureOptions {
|
|
32
|
+
cwd?: string;
|
|
33
|
+
env?: NodeJS.ProcessEnv;
|
|
34
|
+
timeoutMs: number;
|
|
35
|
+
maxOutputBytes?: number;
|
|
36
|
+
}
|
|
37
|
+
export interface CapturedProcessResult {
|
|
38
|
+
status: number | null;
|
|
39
|
+
signal: NodeJS.Signals | null;
|
|
40
|
+
stdout: string;
|
|
41
|
+
stderr: string;
|
|
42
|
+
error?: string;
|
|
43
|
+
timedOut: boolean;
|
|
44
|
+
}
|
|
45
|
+
export declare function killProcessGroup(pgid: number): void;
|
|
46
|
+
/**
|
|
47
|
+
* Byte-bounded UTF-8 output accumulator shared by the capture paths
|
|
48
|
+
* (spawnCapture here, executeTarget/executeRemoteSpec in executor.ts).
|
|
49
|
+
*
|
|
50
|
+
* - Callers feed decoded strings (`stream.setEncoding("utf8")` keeps
|
|
51
|
+
* multi-byte sequences split across pipe chunks intact); the buffer never
|
|
52
|
+
* decodes chunks itself.
|
|
53
|
+
* - Truncation keeps at most `maxBytes` BYTES of tail, cutting at a UTF-8
|
|
54
|
+
* sequence boundary so the retained text never starts mid-character.
|
|
55
|
+
* - The truncation marker reports the CUMULATIVE dropped byte count.
|
|
56
|
+
* - The accumulated text is scrubbed BEFORE every cut: truncation can bisect
|
|
57
|
+
* a credential so the surviving fragment no longer matches any scrub
|
|
58
|
+
* pattern, while scrubbing first turns the intact token into [SCRUBBED]
|
|
59
|
+
* and the cut then slices through the marker harmlessly (scrubSecrets is
|
|
60
|
+
* idempotent, so store-time re-scrubbing stays safe).
|
|
61
|
+
*/
|
|
62
|
+
export declare class BoundedOutputBuffer {
|
|
63
|
+
private readonly maxBytes;
|
|
64
|
+
private text;
|
|
65
|
+
private truncatedBytes;
|
|
66
|
+
constructor(maxBytes: number);
|
|
67
|
+
append(chunk: string): void;
|
|
68
|
+
value(): string;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Async replacement for short spawnSync calls: never blocks the event loop and
|
|
72
|
+
* always enforces an explicit timeout that kills the child's process group.
|
|
73
|
+
*/
|
|
74
|
+
export declare function spawnCapture(command: string, args: string[], opts: SpawnCaptureOptions): Promise<CapturedProcessResult>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface BackupDatabaseOptions {
|
|
2
|
+
/** Why this backup is being taken (e.g. "pre-migration", "daily"). Debounce and retention are tracked per reason. */
|
|
3
|
+
reason: string;
|
|
4
|
+
/** How many backups to retain per reason (newest first). */
|
|
5
|
+
keep?: number;
|
|
6
|
+
/** Source database file; defaults to the live store path. Tests must pass a temp path. */
|
|
7
|
+
dbFile?: string;
|
|
8
|
+
/** Directory for backup files; defaults to `<db dir>/backups`. */
|
|
9
|
+
backupsDir?: string;
|
|
10
|
+
/** Injectable clock for tests. */
|
|
11
|
+
now?: Date;
|
|
12
|
+
}
|
|
13
|
+
export interface BackupDatabaseResult {
|
|
14
|
+
/** Absolute path of the backup that was written (absent when skipped). */
|
|
15
|
+
path?: string;
|
|
16
|
+
skipped: boolean;
|
|
17
|
+
skipReason?: string;
|
|
18
|
+
/** Backups removed by retention pruning. */
|
|
19
|
+
prunedPaths: string[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Snapshot the loops database with `VACUUM INTO`. Per-reason backups are
|
|
23
|
+
* debounced to at most one per hour and pruned to the `keep` most recent.
|
|
24
|
+
*/
|
|
25
|
+
export declare function backupDatabase(opts: BackupDatabaseOptions): BackupDatabaseResult;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coded errors thrown by the store and CLI layers. Each class carries a stable
|
|
3
|
+
* machine-readable `.code` so callers can branch on failure kind without
|
|
4
|
+
* parsing human-readable messages.
|
|
5
|
+
*/
|
|
6
|
+
export declare class CodedError extends Error {
|
|
7
|
+
readonly code: string;
|
|
8
|
+
constructor(code: string, message: string);
|
|
9
|
+
}
|
|
10
|
+
export declare class LoopNotFoundError extends CodedError {
|
|
11
|
+
constructor(idOrName: string);
|
|
12
|
+
}
|
|
13
|
+
export declare class LoopArchivedError extends CodedError {
|
|
14
|
+
constructor(idOrName: string);
|
|
15
|
+
}
|
|
16
|
+
export declare class AmbiguousNameError extends CodedError {
|
|
17
|
+
constructor(name: string);
|
|
18
|
+
}
|
|
19
|
+
export declare class ValidationError extends CodedError {
|
|
20
|
+
constructor(message: string);
|
|
21
|
+
}
|
package/dist/lib/executor.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { LanguageModel } from "ai";
|
|
2
|
-
import type { ExecutableTarget, ExecutorResult, Loop, LoopMachineRef, LoopRun, PersistGuardOptions } from "../types.js";
|
|
2
|
+
import type { AgentTarget, ExecutableTarget, ExecutorResult, Loop, LoopMachineRef, LoopRun, PersistGuardOptions } from "../types.js";
|
|
3
|
+
export interface SpawnedProcessInfo {
|
|
4
|
+
pid: number;
|
|
5
|
+
pgid: number;
|
|
6
|
+
processStartedAt: string;
|
|
7
|
+
}
|
|
3
8
|
export interface ExecuteOptions extends PersistGuardOptions {
|
|
4
9
|
maxOutputBytes?: number;
|
|
5
10
|
env?: NodeJS.ProcessEnv;
|
|
@@ -7,6 +12,8 @@ export interface ExecuteOptions extends PersistGuardOptions {
|
|
|
7
12
|
log?: (message: string) => void;
|
|
8
13
|
signal?: AbortSignal;
|
|
9
14
|
onSpawn?: (pid: number) => void;
|
|
15
|
+
/** Children are spawned detached in their own process group, so pgid === pid. */
|
|
16
|
+
onSpawnProcess?: (info: SpawnedProcessInfo) => void;
|
|
10
17
|
machine?: LoopMachineRef;
|
|
11
18
|
machineResolver?: (machine: LoopMachineRef) => LoopMachineRef;
|
|
12
19
|
machineCommandResolver?: (machineId: string, command: string) => MachineCommandPlan;
|
|
@@ -34,6 +41,8 @@ interface MachineCommandPlan {
|
|
|
34
41
|
args: string[];
|
|
35
42
|
source: string;
|
|
36
43
|
}
|
|
44
|
+
/** Exported for tests: resolves the default idle watchdog for agent targets. */
|
|
45
|
+
export declare function defaultAgentIdleTimeoutMs(target: AgentTarget, opts: ExecuteOptions): number | undefined;
|
|
37
46
|
export declare function preflightTarget(target: ExecutableTarget, metadata?: ExecutionMetadata, opts?: ExecuteOptions): PreflightResult;
|
|
38
47
|
export declare function executeTarget(target: ExecutableTarget, metadata?: ExecutionMetadata, opts?: ExecuteOptions): Promise<ExecutorResult>;
|
|
39
48
|
export declare function executeLoop(loop: Loop, run: LoopRun, opts?: ExecuteOptions): Promise<ExecutorResult>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Loop, LoopRun, WorkflowSpec } from "../../types.js";
|
|
2
|
+
import type { Goal, GoalExecutionContext, GoalPlanNode } from "./types.js";
|
|
3
|
+
export interface GoalContextParts {
|
|
4
|
+
loop?: Pick<Loop, "id" | "name">;
|
|
5
|
+
loopRun?: Pick<LoopRun, "id" | "scheduledFor">;
|
|
6
|
+
scheduledFor?: string;
|
|
7
|
+
workflow?: Pick<WorkflowSpec, "id" | "name">;
|
|
8
|
+
workflowRunId?: string;
|
|
9
|
+
workflowStepId?: string;
|
|
10
|
+
}
|
|
11
|
+
/** Single source for the goal execution context previously copy-pasted across runners. */
|
|
12
|
+
export declare function goalExecutionContext(parts: GoalContextParts): GoalExecutionContext;
|
|
13
|
+
/** Executor metadata (LOOPS_* env) derived from a goal execution context; loopRunId maps to runId. */
|
|
14
|
+
export declare function executionMetadata(context: GoalExecutionContext | undefined, goal?: Pick<Goal, "goalId" | "objective">, node?: Pick<GoalPlanNode, "key">): Record<string, string | undefined>;
|
|
15
|
+
/** Exposes the executing plan node to child processes so command targets can honor node objectives. */
|
|
16
|
+
export declare function withGoalNodeEnv(env: NodeJS.ProcessEnv | undefined, node: Pick<GoalPlanNode, "key" | "objective">): NodeJS.ProcessEnv;
|
|
@@ -7,3 +7,4 @@ export interface ResolveGoalModelOptions {
|
|
|
7
7
|
}
|
|
8
8
|
export declare const DEFAULT_GOAL_MODEL = "openai/gpt-4o-mini";
|
|
9
9
|
export declare function resolveGoalModel(opts?: ResolveGoalModelOptions): LanguageModel;
|
|
10
|
+
export declare function resolveGoalVerifierModel(opts?: ResolveGoalModelOptions): LanguageModel;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import type { ExecutableTarget } from "../../types.js";
|
|
1
2
|
import type { Goal, GoalPlanNode, GoalSpec } from "./types.js";
|
|
2
3
|
export declare function planPrompt(spec: GoalSpec): string;
|
|
3
|
-
export declare function iterationPrompt(goal: Goal,
|
|
4
|
+
export declare function iterationPrompt(goal: Pick<Goal, "objective">, node: Pick<GoalPlanNode, "key" | "objective">): string;
|
|
5
|
+
export declare function goalNodeTarget(target: ExecutableTarget, goal: Pick<Goal, "objective">, node: Pick<GoalPlanNode, "key" | "objective">): ExecutableTarget;
|
|
4
6
|
export declare function achievementPrompt(goal: Goal, nodes: GoalPlanNode[], evidence: string[]): string;
|
package/dist/lib/goal/types.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export interface GoalSpec {
|
|
|
11
11
|
objective: string;
|
|
12
12
|
tokenBudget?: number;
|
|
13
13
|
model?: string;
|
|
14
|
+
/** Independent model for the achievement audit; defaults to LOOPS_GOAL_VERIFIER_MODEL, then the planner model. */
|
|
15
|
+
verifierModel?: string;
|
|
14
16
|
maxTurns?: number;
|
|
15
17
|
maxTokens?: number;
|
|
16
18
|
autoExecute?: GoalAutoExecute;
|
|
@@ -105,6 +107,7 @@ export interface GoalExecutionContext {
|
|
|
105
107
|
export interface RunGoalOptions {
|
|
106
108
|
store?: Store;
|
|
107
109
|
model?: LanguageModel;
|
|
110
|
+
verifierModel?: LanguageModel;
|
|
108
111
|
target?: ExecutableTarget;
|
|
109
112
|
executeNode?: (node: GoalPlanNode, metadata: Record<string, string | undefined>) => Promise<ExecutorResult>;
|
|
110
113
|
env?: NodeJS.ProcessEnv;
|
package/dist/lib/health.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Loop, LoopRun } from "../types.js";
|
|
2
2
|
import type { Store } from "./store.js";
|
|
3
|
-
export type RunFailureClassification = "rate_limit" | "auth" | "model_not_found" | "context_length" | "schema_response_format" | "node_init" | "preflight" | "route_functional" | "timeout" | "sigsegv" | "skipped_previous_active" | "unknown";
|
|
3
|
+
export type RunFailureClassification = "rate_limit" | "auth" | "model_not_found" | "context_length" | "schema_response_format" | "node_init" | "preflight" | "route_functional" | "timeout" | "sigsegv" | "skipped_previous_active" | "circuit_breaker" | "unknown";
|
|
4
4
|
export interface RunFailureSignal {
|
|
5
5
|
classification: RunFailureClassification;
|
|
6
6
|
fingerprint: string;
|
package/dist/lib/ids.d.ts
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Generate a 128-bit, time-sortable identifier (ULID-like): a 48-bit
|
|
3
|
+
* millisecond timestamp prefix (12 lowercase hex chars) followed by 80 random
|
|
4
|
+
* bits (20 lowercase hex chars). Fixed width keeps lexicographic order aligned
|
|
5
|
+
* with creation time, and plain lowercase hex stays compatible with the
|
|
6
|
+
* existing TEXT primary keys written by earlier releases.
|
|
7
|
+
*/
|
|
8
|
+
export declare function genId(): string;
|
|
2
9
|
export declare function nowIso(): string;
|
package/dist/lib/machines.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { LoopMachineRef } from "../types.js";
|
|
2
|
+
export interface MachineCommandPlan {
|
|
3
|
+
command: string;
|
|
4
|
+
args: string[];
|
|
5
|
+
source: string;
|
|
6
|
+
}
|
|
2
7
|
export interface OpenMachineSummary {
|
|
3
8
|
id: string;
|
|
4
9
|
hostname?: string;
|
|
@@ -13,4 +18,5 @@ export interface OpenMachineSummary {
|
|
|
13
18
|
}
|
|
14
19
|
export declare function listOpenMachines(): OpenMachineSummary[];
|
|
15
20
|
export declare function resolveLoopMachine(machineId: string): LoopMachineRef;
|
|
21
|
+
export declare function resolveMachineCommand(machineId: string, command: string): MachineCommandPlan;
|
|
16
22
|
export declare function refreshLoopMachine(machine: LoopMachineRef): LoopMachineRef;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const START_TIME_TOLERANCE_MS = 5000;
|
|
2
|
+
export declare function procStatFields(path: string): string[] | undefined;
|
|
3
|
+
export declare function processStartTimeMs(pid: number): number | undefined;
|
|
4
|
+
/**
|
|
5
|
+
* Lenient fingerprint comparison: unknown data (no recorded start time, an
|
|
6
|
+
* unparsable record, or an unresolvable actual start time) counts as a match.
|
|
7
|
+
* Use for non-destructive liveness checks where "possibly alive" is the safe
|
|
8
|
+
* answer (pidfile checks, deciding not to take over a lease).
|
|
9
|
+
*/
|
|
10
|
+
export declare function sameProcessStart(recorded: string | number | undefined, actualMs: number | undefined, toleranceMs?: number): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Strict fingerprint comparison for destructive paths (signalling processes):
|
|
13
|
+
* true only when both the recorded and actual start times resolve and match
|
|
14
|
+
* within tolerance. Unverifiable identity fails closed as a mismatch.
|
|
15
|
+
*/
|
|
16
|
+
export declare function verifiedProcessStart(recorded: string | number | undefined, actualMs: number | undefined, toleranceMs?: number): boolean;
|
|
@@ -16,6 +16,7 @@ export interface DuePlan {
|
|
|
16
16
|
slots: string[];
|
|
17
17
|
skippedToNextRunAt?: string;
|
|
18
18
|
}
|
|
19
|
+
export declare const MAX_CATCH_UP_SLOTS = 1000;
|
|
19
20
|
export declare function dueSlots(loop: Loop, now: Date): DuePlan;
|
|
20
21
|
export declare function parseDuration(input: string): number;
|
|
21
22
|
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write-path secret scrubbing. `scrubSecrets` removes credential material from
|
|
3
|
+
* text before it is persisted (run stdout/stderr/error, goal evidence, raw
|
|
4
|
+
* model responses). Display-layer truncation/redaction lives in format.ts;
|
|
5
|
+
* this module is about never writing recoverable secrets to disk at all.
|
|
6
|
+
*
|
|
7
|
+
* The function is idempotent (`scrubSecrets(scrubSecrets(x)) === scrubSecrets(x)`)
|
|
8
|
+
* and linear over the input, so it stays fast on multi-hundred-KB outputs.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Replace credential tokens in `text` with "[SCRUBBED]". Idempotent and safe
|
|
12
|
+
* to run on JSON-encoded payloads (replacements never introduce quotes).
|
|
13
|
+
*/
|
|
14
|
+
export declare function scrubSecrets(text: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Recursively scrub the string leaves of a JSON-serializable value. Run this
|
|
17
|
+
* BEFORE `JSON.stringify` when persisting structured payloads (goal evidence,
|
|
18
|
+
* raw model responses): stringify escapes quotes, which would otherwise hide
|
|
19
|
+
* quoted secrets from the flat text patterns above.
|
|
20
|
+
*/
|
|
21
|
+
export declare function scrubSecretsDeep<T>(value: T): T;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface RouteSelection<T> {
|
|
2
|
+
selected: T[];
|
|
3
|
+
cursor: {
|
|
4
|
+
key: string;
|
|
5
|
+
total: number;
|
|
6
|
+
maxActions: number;
|
|
7
|
+
previousFingerprint?: string;
|
|
8
|
+
startIndex: number;
|
|
9
|
+
lastFingerprint?: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export declare function writeRouteCursor(key: string, lastFingerprint: string | undefined): void;
|
|
13
|
+
export declare function writeRouteEvidence(kind: string, value: unknown, evidenceDir: string | undefined): string | undefined;
|
|
14
|
+
export declare function selectRouteItems<T>(items: T[], maxActions: number, cursorKey: string, fingerprintOf: (item: T) => string): RouteSelection<T>;
|
|
15
|
+
export declare function routeCursorKey(kind: "health" | "hygiene", parts: unknown[], opts: {
|
|
16
|
+
autoRoute?: boolean;
|
|
17
|
+
routeProjectPath?: string;
|
|
18
|
+
}): string;
|
|
@@ -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
|
+
};
|