@obversa/runtime 0.1.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/LICENSE +21 -0
- package/README.md +192 -0
- package/dist/api.d.ts +72 -0
- package/dist/api.js +9775 -0
- package/dist/api.js.map +1 -0
- package/dist/artifacts/conformance.d.ts +1 -0
- package/dist/artifacts/file-store.d.ts +8 -0
- package/dist/artifacts/store.d.ts +1 -0
- package/dist/callback/approval.d.ts +8 -0
- package/dist/callback/client.d.ts +38 -0
- package/dist/callback/gate.d.ts +1 -0
- package/dist/callback/stored-client.d.ts +5 -0
- package/dist/chunk-3L6YNPN6.js +3 -0
- package/dist/chunk-3L6YNPN6.js.map +1 -0
- package/dist/chunk-5GLEABOU.js +3 -0
- package/dist/chunk-5GLEABOU.js.map +1 -0
- package/dist/chunk-DEW5R23M.js +338 -0
- package/dist/chunk-DEW5R23M.js.map +1 -0
- package/dist/chunk-DV5P4QLI.js +3056 -0
- package/dist/chunk-DV5P4QLI.js.map +1 -0
- package/dist/chunk-NIBHM5I5.js +34 -0
- package/dist/chunk-NIBHM5I5.js.map +1 -0
- package/dist/chunk-RZLMX3IA.js +368 -0
- package/dist/chunk-RZLMX3IA.js.map +1 -0
- package/dist/core/agent-md.d.ts +36 -0
- package/dist/core/agent.d.ts +86 -0
- package/dist/core/approval-job.d.ts +43 -0
- package/dist/core/assert-graph.d.ts +34 -0
- package/dist/core/budget.d.ts +49 -0
- package/dist/core/concurrency.d.ts +2 -0
- package/dist/core/condition.d.ts +209 -0
- package/dist/core/context.d.ts +36 -0
- package/dist/core/cost.d.ts +59 -0
- package/dist/core/dag.d.ts +20 -0
- package/dist/core/decision.d.ts +29 -0
- package/dist/core/describe.d.ts +56 -0
- package/dist/core/engine-meta.d.ts +5 -0
- package/dist/core/env-overlay.d.ts +35 -0
- package/dist/core/errors.d.ts +46 -0
- package/dist/core/feedback.d.ts +68 -0
- package/dist/core/git.d.ts +152 -0
- package/dist/core/guards.d.ts +70 -0
- package/dist/core/isolated.d.ts +40 -0
- package/dist/core/job.d.ts +134 -0
- package/dist/core/limits.d.ts +22 -0
- package/dist/core/loop.d.ts +24 -0
- package/dist/core/merge.d.ts +30 -0
- package/dist/core/pipeline.d.ts +35 -0
- package/dist/core/process.d.ts +11 -0
- package/dist/core/progress.d.ts +82 -0
- package/dist/core/redact.d.ts +1 -0
- package/dist/core/stats.d.ts +63 -0
- package/dist/core/team.d.ts +34 -0
- package/dist/core/text.d.ts +8 -0
- package/dist/core/tournament.d.ts +25 -0
- package/dist/core/types.d.ts +650 -0
- package/dist/engines/command-runner.d.ts +1 -0
- package/dist/engines/conformance.d.ts +1 -0
- package/dist/engines/engine.d.ts +4 -0
- package/dist/engines/failure.d.ts +1 -0
- package/dist/engines/fallback.d.ts +35 -0
- package/dist/engines/message-map.d.ts +1 -0
- package/dist/engines/mock.d.ts +1 -0
- package/dist/engines/preflight.d.ts +36 -0
- package/dist/env/command.d.ts +50 -0
- package/dist/env/command.js +65 -0
- package/dist/env/command.js.map +1 -0
- package/dist/env/environment.d.ts +4 -0
- package/dist/env/mock.d.ts +24 -0
- package/dist/events/conformance.d.ts +1 -0
- package/dist/events/envelope.d.ts +1 -0
- package/dist/events/jsonl-store.d.ts +8 -0
- package/dist/events/store.d.ts +1 -0
- package/dist/graph/commands.d.ts +5 -0
- package/dist/graph/conformance.d.ts +32 -0
- package/dist/graph/kernel.d.ts +5 -0
- package/dist/graph/plan.d.ts +1 -0
- package/dist/graph/type.d.ts +7 -0
- package/dist/graph/value.d.ts +1 -0
- package/dist/graph-types/dag.d.ts +137 -0
- package/dist/graph-types/loop.d.ts +194 -0
- package/dist/graph-types/team.d.ts +68 -0
- package/dist/memory.d.ts +82 -0
- package/dist/memory.js +397 -0
- package/dist/memory.js.map +1 -0
- package/dist/proof/acceptance.d.ts +5 -0
- package/dist/proof/artifact.d.ts +6 -0
- package/dist/proof/cache.d.ts +4 -0
- package/dist/runtime/attempt.d.ts +19 -0
- package/dist/runtime/budget.d.ts +4 -0
- package/dist/runtime/engine-availability.d.ts +18 -0
- package/dist/runtime/graph-executor.d.ts +7 -0
- package/dist/runtime/monitor.d.ts +80 -0
- package/dist/runtime/node-lifecycle.d.ts +84 -0
- package/dist/runtime/paths.d.ts +2 -0
- package/dist/runtime/persist.d.ts +31 -0
- package/dist/runtime/preflight-record.d.ts +149 -0
- package/dist/runtime/process-tree.d.ts +1 -0
- package/dist/runtime/result-contract.d.ts +4 -0
- package/dist/runtime/result-parts.d.ts +1 -0
- package/dist/runtime/run-definition.d.ts +24 -0
- package/dist/runtime/run-event.d.ts +9 -0
- package/dist/runtime/runner.d.ts +139 -0
- package/dist/runtime/supervisor.d.ts +111 -0
- package/dist/runtime/team-rooms.d.ts +13 -0
- package/dist/runtime/workspace-policy.d.ts +25 -0
- package/dist/storage/error.d.ts +1 -0
- package/dist/storage/id.d.ts +1 -0
- package/dist/storage/local.d.ts +12 -0
- package/dist/storage/local.js +1492 -0
- package/dist/storage/local.js.map +1 -0
- package/dist/testing.d.ts +15 -0
- package/dist/testing.js +274 -0
- package/dist/testing.js.map +1 -0
- package/dist/workflow-agent-response.d.ts +3 -0
- package/dist/workflow-support.d.ts +36 -0
- package/dist/workflow-support.js +5 -0
- package/dist/workflow-support.js.map +1 -0
- package/dist/workflow.d.ts +73 -0
- package/dist/workspace/conformance.d.ts +1 -0
- package/dist/workspace/git-provider.d.ts +26 -0
- package/dist/workspace/provider.d.ts +2 -0
- package/package.json +91 -0
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The core contract: one universal runnable unit and two supporting types.
|
|
3
|
+
*
|
|
4
|
+
* - a `Job` — a unit of work that runs once and returns an `Outcome`.
|
|
5
|
+
* Any size: a single agent turn, or a whole nested loop.
|
|
6
|
+
* - a `Condition` — a question answered against the current context (a `when`).
|
|
7
|
+
* - a `Loop` — produced by `loop()`, and is itself a `Job`.
|
|
8
|
+
*
|
|
9
|
+
* Because a `Loop` is a `Job`, a loop's `body`/`review`/any stage can be
|
|
10
|
+
* another `loop(...)`, so loop jobs nest.
|
|
11
|
+
*
|
|
12
|
+
* Jenkins mapping: Job≈job/pipeline, Engine≈agent/node (where it runs),
|
|
13
|
+
* `start`≈trigger, `Condition`≈`when`, `review`+`onComplete`≈`post`,
|
|
14
|
+
* `retry`≈`retry`/`catchError`. The stage/DAG machinery is not imported here:
|
|
15
|
+
* the primitive is the loop, not a pipeline.
|
|
16
|
+
*/
|
|
17
|
+
import type { Engine, EngineRef, UsageReceipt } from '../engines/engine.js';
|
|
18
|
+
import type { Memory } from '@obversa/api';
|
|
19
|
+
import type { LoopError } from './errors.js';
|
|
20
|
+
import type { EnvHandle, Environment } from '../env/environment.js';
|
|
21
|
+
import type { JsonValue, RunBrief } from '../graph/value.js';
|
|
22
|
+
import type { CallbackEvent, ClaimResult, ReleaseResult, SubmitResult } from '../callback/client.js';
|
|
23
|
+
import type { CallbackRequest } from '../callback/gate.js';
|
|
24
|
+
/**
|
|
25
|
+
* The client a run's questions go through: the in-memory `CallbackClient`,
|
|
26
|
+
* or the stored client (`createStoredCallbackClient`) whose questions survive
|
|
27
|
+
* a process exit. Each method may answer at once or as a promise; a step
|
|
28
|
+
* that asks awaits both shapes alike. Written as the awaited shape here, so
|
|
29
|
+
* the core types import no storage module.
|
|
30
|
+
*/
|
|
31
|
+
export interface RunCallbacks {
|
|
32
|
+
post(request: CallbackRequest): void | Promise<void>;
|
|
33
|
+
listPending(): readonly CallbackRequest[] | Promise<readonly CallbackRequest[]>;
|
|
34
|
+
claim(requestId: string, routerId: string): ClaimResult | Promise<ClaimResult>;
|
|
35
|
+
submit(requestId: string, claimToken: string, routerId: string, requestDigest: string, response: JsonValue): SubmitResult | Promise<SubmitResult>;
|
|
36
|
+
release(requestId: string, claimToken: string): ReleaseResult | Promise<ReleaseResult>;
|
|
37
|
+
supersede(requestId: string, supersededBy: string): void | Promise<void>;
|
|
38
|
+
history(requestId?: string): readonly CallbackEvent[] | Promise<readonly CallbackEvent[]>;
|
|
39
|
+
}
|
|
40
|
+
/** Terminal disposition of a `Job`. */
|
|
41
|
+
export type OutcomeStatus = 'pass' | 'fail' | 'aborted' | 'exhausted' | 'paused';
|
|
42
|
+
/**
|
|
43
|
+
* How the run reacts to a provider rate limit, account/usage allowance, or its
|
|
44
|
+
* own token budget. `auto` (the default) waits when the reset is known and
|
|
45
|
+
* within `maxWaitMs`, else pauses the run.
|
|
46
|
+
*/
|
|
47
|
+
export type LimitPolicy = 'auto' | 'wait' | 'exit' | 'fail';
|
|
48
|
+
export interface Outcome {
|
|
49
|
+
status: OutcomeStatus;
|
|
50
|
+
/** 0..1 confidence, when the outcome was decided by an agent validator. */
|
|
51
|
+
confidence?: number;
|
|
52
|
+
/**
|
|
53
|
+
* True when an engine finished after its soft timeout but before the hard
|
|
54
|
+
* timeout/grace boundary. The result is usable, but supervisors can still
|
|
55
|
+
* surface that it landed late.
|
|
56
|
+
*/
|
|
57
|
+
late?: boolean;
|
|
58
|
+
/** One-line human summary for logs and host status views. */
|
|
59
|
+
summary?: string;
|
|
60
|
+
/** Arbitrary payload threaded to the next step / surfaced to the caller. */
|
|
61
|
+
data?: unknown;
|
|
62
|
+
/** Present when `status` is driven by a failure. */
|
|
63
|
+
error?: LoopError;
|
|
64
|
+
/**
|
|
65
|
+
* Present when a loop ended `exhausted` because its `noProgress` detector
|
|
66
|
+
* tripped: the evidence that the last `window` iterations reached no state
|
|
67
|
+
* the run had not already seen. Lets a supervisor distinguish a stall from a
|
|
68
|
+
* hit iteration cap without parsing the summary.
|
|
69
|
+
*/
|
|
70
|
+
stall?: StallReport;
|
|
71
|
+
/**
|
|
72
|
+
* Structured feedback asking an earlier unit of work for another pass, and the
|
|
73
|
+
* single channel for it. When `revision.target` is set, the enclosing `dag`
|
|
74
|
+
* re-runs that node and its transitive dependents with `revision.reason`
|
|
75
|
+
* threaded in as `lastReview`, bounded by `DagConfig.maxKickbacks` (default
|
|
76
|
+
* 0 — ignored). The re-run happens in execution only; the graph stays acyclic
|
|
77
|
+
* and the re-run budget guarantees termination. Produce one with
|
|
78
|
+
* `revisionRequest({ target, findings })` or `kickback(to, reason)`.
|
|
79
|
+
*/
|
|
80
|
+
revision?: RevisionRequest;
|
|
81
|
+
}
|
|
82
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
83
|
+
/**
|
|
84
|
+
* Where a job's code lives: a working directory and (when it is a git repo) the
|
|
85
|
+
* branch checked out there. Sequential jobs share one `Workspace`; concurrent
|
|
86
|
+
* writers can fork into isolated worktrees. Default: the process working directory.
|
|
87
|
+
*/
|
|
88
|
+
export interface Workspace {
|
|
89
|
+
/** Absolute path to the working tree this job operates in. */
|
|
90
|
+
readonly dir: string;
|
|
91
|
+
/** The branch checked out in `dir`, when known (undefined on detached HEAD). */
|
|
92
|
+
readonly branch?: string;
|
|
93
|
+
}
|
|
94
|
+
export type FeedbackActionSeverity = 'block' | 'should-fix' | 'nice-to-have' | 'approve';
|
|
95
|
+
export type FeedbackSeverity = FeedbackActionSeverity;
|
|
96
|
+
export type FeedbackDecision = 'accepted' | 'rejected' | 'deferred' | 'escalated';
|
|
97
|
+
export interface FeedbackFinding {
|
|
98
|
+
reviewer?: string;
|
|
99
|
+
severity?: FeedbackSeverity;
|
|
100
|
+
decision?: FeedbackDecision;
|
|
101
|
+
/**
|
|
102
|
+
* The ownership surface this finding belongs to. A review/fix loop may be
|
|
103
|
+
* scoped to a smaller surface and escalate findings outside it instead of
|
|
104
|
+
* counting them against convergence.
|
|
105
|
+
*/
|
|
106
|
+
scope?: string;
|
|
107
|
+
evidence: string;
|
|
108
|
+
recommendation?: string;
|
|
109
|
+
}
|
|
110
|
+
export type RevisionRerun = 'target-and-dependents';
|
|
111
|
+
export interface RevisionRequest {
|
|
112
|
+
target?: string;
|
|
113
|
+
reason: string;
|
|
114
|
+
findings?: FeedbackFinding[];
|
|
115
|
+
rerun?: RevisionRerun;
|
|
116
|
+
source?: string;
|
|
117
|
+
decision?: FeedbackDecision;
|
|
118
|
+
}
|
|
119
|
+
export interface GraphPosition {
|
|
120
|
+
dag: string;
|
|
121
|
+
node: string;
|
|
122
|
+
/** Which run of this node the DAG is executing, starting at 1. */
|
|
123
|
+
attempt?: number;
|
|
124
|
+
path: readonly string[];
|
|
125
|
+
needs: readonly string[];
|
|
126
|
+
dependents: readonly string[];
|
|
127
|
+
/** One sentence describing what this node does, when declared. */
|
|
128
|
+
desc?: string;
|
|
129
|
+
/** The written acceptance criterion for this node, when declared. */
|
|
130
|
+
gate?: string;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Threaded into every `Job`. Carries the engine, the abort signal, the event
|
|
134
|
+
* sink, a mutable scratchpad shared across the run, the workspace the work
|
|
135
|
+
* happens in, and the position in the loop tree (used by hosts and stats).
|
|
136
|
+
*/
|
|
137
|
+
export interface JobContext {
|
|
138
|
+
/** Default engine for this run, when the host supplied one. */
|
|
139
|
+
readonly engine?: Engine;
|
|
140
|
+
/**
|
|
141
|
+
* Resolve an engine for a step. A name comes only from the host-supplied
|
|
142
|
+
* map; a ready-made `Engine` passes through; no argument uses the run default.
|
|
143
|
+
*/
|
|
144
|
+
resolveEngine(ref?: EngineRef): Engine;
|
|
145
|
+
readonly signal: AbortSignal;
|
|
146
|
+
/** Stable id for this run when one was assigned by the runner. */
|
|
147
|
+
readonly runId?: string;
|
|
148
|
+
emit(event: LoopEvent): void;
|
|
149
|
+
/** Immutable run brief shared by every job in this run. */
|
|
150
|
+
readonly params: RunBrief;
|
|
151
|
+
/** Shared mutable state for the whole run (e.g. accumulating notes). */
|
|
152
|
+
readonly state: Record<string, unknown>;
|
|
153
|
+
/** Memory available to jobs in this run, when the caller supplied it. */
|
|
154
|
+
readonly memory?: Memory;
|
|
155
|
+
/**
|
|
156
|
+
* The run's callbacks client: where a step posts a question for a person or
|
|
157
|
+
* an outside router, and where the answer is found again on a resume. Every
|
|
158
|
+
* run has one, a fresh in-memory client by default; pass `callbacks` to
|
|
159
|
+
* `run` to keep questions across runs.
|
|
160
|
+
*/
|
|
161
|
+
readonly callbacks?: RunCallbacks;
|
|
162
|
+
/** Wait for an outside answer, or return paused (the default). */
|
|
163
|
+
readonly onCallback?: 'wait' | 'exit';
|
|
164
|
+
/** Where this job's code lives — the working dir and branch (the substrate). */
|
|
165
|
+
readonly workspace: Workspace;
|
|
166
|
+
/** The running environment for this workspace, when one is up (gate target). */
|
|
167
|
+
readonly environment?: EnvHandle;
|
|
168
|
+
/**
|
|
169
|
+
* Env vars pinned for this scope and everything beneath it — gate commands,
|
|
170
|
+
* judge calls, and the subprocesses agent leaves spawn. Layered over
|
|
171
|
+
* `ctx.environment?.env`; set via `withEnv()`.
|
|
172
|
+
*/
|
|
173
|
+
readonly envOverlay?: Record<string, string>;
|
|
174
|
+
/** 1-based iteration index within the enclosing loop; 0 outside a loop. */
|
|
175
|
+
readonly iteration: number;
|
|
176
|
+
/** Nesting depth (root steps are 0). */
|
|
177
|
+
readonly depth: number;
|
|
178
|
+
/** Loop/step names from the root down to here. */
|
|
179
|
+
readonly path: readonly string[];
|
|
180
|
+
/** The current DAG node position, when this job is running inside a dag node. */
|
|
181
|
+
readonly graph?: GraphPosition;
|
|
182
|
+
/**
|
|
183
|
+
* Timeout inherited by jobs in this scope. A node can set it once and agent
|
|
184
|
+
* leaves beneath it receive the same cap unless they override it directly.
|
|
185
|
+
*/
|
|
186
|
+
readonly timeoutMs?: number;
|
|
187
|
+
/** Extra hard-timeout window after `timeoutMs` for accepting a completed turn. */
|
|
188
|
+
readonly timeoutGraceMs?: number;
|
|
189
|
+
/**
|
|
190
|
+
* Inside a `dag` node: the outcomes of the nodes this node `needs`, by the
|
|
191
|
+
* same names its config uses (`needs: ['test']` gives `ctx.needs.test`). A
|
|
192
|
+
* branch's `when` reads the deciding node's outcome here, so a command can
|
|
193
|
+
* choose the path the graph takes next with no agent deciding. Undefined
|
|
194
|
+
* outside a dag node.
|
|
195
|
+
*/
|
|
196
|
+
readonly needs?: Readonly<Record<string, Outcome>>;
|
|
197
|
+
/** The previous body outcome in the enclosing loop (used by `review`/gates). */
|
|
198
|
+
readonly lastOutcome?: Outcome;
|
|
199
|
+
/** The most recent failed-review outcome, so a restart can act on it. */
|
|
200
|
+
readonly lastReview?: Outcome;
|
|
201
|
+
/**
|
|
202
|
+
* The previous iteration's explicit `until`-gate evaluation (met or not),
|
|
203
|
+
* including its diagnostic `output`. Undefined when the loop has no explicit
|
|
204
|
+
* `until`, on the first iteration, and outside loop jobs.
|
|
205
|
+
*/
|
|
206
|
+
readonly lastGate?: ConditionResult;
|
|
207
|
+
/** How a loop reacts to a rate/quota/budget limit. Default `auto`. */
|
|
208
|
+
readonly onLimit: LimitPolicy;
|
|
209
|
+
/** Cap on an interruptible limit-wait under `auto`/`wait`. */
|
|
210
|
+
readonly maxWaitMs: number;
|
|
211
|
+
log(message: string, level?: LogLevel): void;
|
|
212
|
+
}
|
|
213
|
+
export interface NoProgressConfig {
|
|
214
|
+
/** Consecutive no-progress iterations before the loop stalls out. Default 3. */
|
|
215
|
+
window?: number;
|
|
216
|
+
/** Confidence improvement required to count as progress. Default 0.02. */
|
|
217
|
+
minConfidenceDelta?: number;
|
|
218
|
+
/** Optional progress state outside the workspace. */
|
|
219
|
+
signal?: (ctx: JobContext, last: Outcome | undefined) => string | number | undefined | Promise<string | number | undefined>;
|
|
220
|
+
/** Read the workspace fingerprint each iteration. Default true. */
|
|
221
|
+
workspace?: boolean;
|
|
222
|
+
/** Fingerprint a failing deterministic gate's output. Default false. */
|
|
223
|
+
gate?: boolean;
|
|
224
|
+
}
|
|
225
|
+
export type NoProgressInput = number | NoProgressConfig;
|
|
226
|
+
export interface StallReport {
|
|
227
|
+
readonly window: number;
|
|
228
|
+
readonly iterations: number[];
|
|
229
|
+
readonly reason: string;
|
|
230
|
+
readonly evidence: string[];
|
|
231
|
+
}
|
|
232
|
+
export type Job = (ctx: JobContext) => Promise<Outcome>;
|
|
233
|
+
/**
|
|
234
|
+
* The introspectable shape of a `Job`, attached by the builders (`loop`, `dag`,
|
|
235
|
+
* `agentJob`, ...) and read back by validation and description tools or any
|
|
236
|
+
* agent that wants to inspect a loop without running it. Held in a side table
|
|
237
|
+
* (see `core/describe.ts`), so the `Job` type stays a plain function. `kind`
|
|
238
|
+
* names the builder; the rest is builder-specific (a loop carries its gate and
|
|
239
|
+
* body, a dag carries its nodes).
|
|
240
|
+
*/
|
|
241
|
+
export interface JobMeta {
|
|
242
|
+
kind: 'loop' | 'dag' | 'agent' | 'fn' | (string & {});
|
|
243
|
+
name?: string;
|
|
244
|
+
[key: string]: unknown;
|
|
245
|
+
}
|
|
246
|
+
export interface ConditionResult {
|
|
247
|
+
met: boolean;
|
|
248
|
+
/** 0..1 when an agent decided this; undefined for deterministic checks. */
|
|
249
|
+
confidence?: number;
|
|
250
|
+
reason: string;
|
|
251
|
+
/**
|
|
252
|
+
* Verbatim diagnostic output backing the verdict — the evidence, not the
|
|
253
|
+
* one-line `reason` (a failing command's stdout/stderr, a judge's full
|
|
254
|
+
* findings). Producers truncate and secret-scrub it. Flows into
|
|
255
|
+
* `loop:condition` events and to the next loop body via `ctx.lastGate`.
|
|
256
|
+
*/
|
|
257
|
+
output?: string;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* The single condition primitive. A question answered against the context and
|
|
261
|
+
* the most recent body outcome. Both deterministic checks and agent validators
|
|
262
|
+
* are this same type — `agentCheck(...)` simply returns one.
|
|
263
|
+
*/
|
|
264
|
+
export type Condition = (ctx: JobContext, last: Outcome | undefined) => Promise<ConditionResult>;
|
|
265
|
+
/** A bare deterministic predicate — accepted anywhere a `Condition` is. */
|
|
266
|
+
export type RawPredicate = (ctx: JobContext, last: Outcome | undefined) => boolean | Promise<boolean>;
|
|
267
|
+
/**
|
|
268
|
+
* What a gate (`start`/`until`/`stopOn`) accepts: one item or many, freely
|
|
269
|
+
* mixing deterministic predicates and agent conditions. Arrays are reduced to
|
|
270
|
+
* the single `Condition` primitive by `toCondition` (default: all must hold;
|
|
271
|
+
* wrap in `any(...)` for or-semantics).
|
|
272
|
+
*/
|
|
273
|
+
export type ConditionInput = Condition | RawPredicate | ConditionInput[];
|
|
274
|
+
export interface RetryPolicy {
|
|
275
|
+
/** On a thrown error in the body: keep looping, or end the loop as failed. */
|
|
276
|
+
onError: 'continue' | 'fail';
|
|
277
|
+
/** Cap on consecutive errored iterations before forcing 'fail'. */
|
|
278
|
+
maxConsecutive?: number;
|
|
279
|
+
backoffMs?: number;
|
|
280
|
+
}
|
|
281
|
+
export interface LoopConfig {
|
|
282
|
+
name: string;
|
|
283
|
+
/** The work done each iteration. Pass another `loop(...)` to nest. */
|
|
284
|
+
body: Job;
|
|
285
|
+
/** Gate before iterating; one or many checks. Unmet => loop is `aborted`. */
|
|
286
|
+
start?: ConditionInput;
|
|
287
|
+
/** After each body run; one or many checks. Met => stop (then `review`). */
|
|
288
|
+
until?: ConditionInput;
|
|
289
|
+
/**
|
|
290
|
+
* Evaluate the explicit `until` gate once at iteration 0, before the body.
|
|
291
|
+
* A met gate completes immediately (after `review`, when configured); an
|
|
292
|
+
* unmet verdict is exposed to the first body iteration as `ctx.lastGate`.
|
|
293
|
+
* Requires `until`.
|
|
294
|
+
*/
|
|
295
|
+
checkFirst?: boolean;
|
|
296
|
+
/** Hard early-exit per iteration; one or many checks. Met => `aborted`. */
|
|
297
|
+
stopOn?: ConditionInput;
|
|
298
|
+
/** Iteration cap. Reached without passing => `exhausted`. */
|
|
299
|
+
max?: number;
|
|
300
|
+
/**
|
|
301
|
+
* The third hard stop, alongside `max` and `budget`: end the loop `exhausted`
|
|
302
|
+
* when this many consecutive iterations make no observable progress — no
|
|
303
|
+
* workspace state the run has not already visited, no custom `signal` value
|
|
304
|
+
* not already seen, no gate confidence beating its previous best. A bare
|
|
305
|
+
* number is the window (`3` ⇒ three flat iterations); pass a `NoProgressConfig`
|
|
306
|
+
* for the full knobs. Off by default: a polling loop legitimately makes no
|
|
307
|
+
* progress until the outside world changes, so this is opt-in like `commit`.
|
|
308
|
+
* The stalled outcome carries the evidence as `Outcome.stall`.
|
|
309
|
+
*/
|
|
310
|
+
noProgress?: NoProgressInput;
|
|
311
|
+
/**
|
|
312
|
+
* Runs when `until` is met. If it returns `pass`, the loop completes.
|
|
313
|
+
* Any other status re-enters the loop — this is the "review fails, run the
|
|
314
|
+
* main loop again" behaviour, and `review` may itself be a `loop(...)`. The
|
|
315
|
+
* failed review outcome is exposed to the next iteration as `ctx.lastReview`.
|
|
316
|
+
*/
|
|
317
|
+
review?: Job;
|
|
318
|
+
/**
|
|
319
|
+
* Cap on consecutive failed reviews before giving up with `exhausted`.
|
|
320
|
+
* Bounds the review-restart cycle independently of `max`; strongly advised
|
|
321
|
+
* when `review` is set with no `max` (otherwise a worker/reviewer standoff
|
|
322
|
+
* never terminates). Default: unbounded (relies on `max`).
|
|
323
|
+
*/
|
|
324
|
+
maxReviewRestarts?: number;
|
|
325
|
+
/** Delay between iterations (polling intervals). Interruptible by abort. */
|
|
326
|
+
delayMs?: number;
|
|
327
|
+
retry?: RetryPolicy;
|
|
328
|
+
/** Side-effect hook after each iteration (logging, custom stats). */
|
|
329
|
+
onIteration?: (outcome: Outcome, ctx: JobContext) => void | Promise<void>;
|
|
330
|
+
/**
|
|
331
|
+
* Post-action run exactly once when the loop ends, whatever the status
|
|
332
|
+
* (Jenkins `post { always }`). For cleanup, notifications, final logging.
|
|
333
|
+
*/
|
|
334
|
+
onComplete?: (outcome: Outcome, ctx: JobContext) => void | Promise<void>;
|
|
335
|
+
}
|
|
336
|
+
export interface DagNode {
|
|
337
|
+
job: Job;
|
|
338
|
+
/**
|
|
339
|
+
* Names of nodes that must finish before this one runs; a required producer
|
|
340
|
+
* must pass, a failed optional producer does not block.
|
|
341
|
+
*/
|
|
342
|
+
needs?: string | string[];
|
|
343
|
+
/** One sentence describing what this node does. */
|
|
344
|
+
desc?: string;
|
|
345
|
+
/** The written acceptance criterion for this node. */
|
|
346
|
+
gate?: string;
|
|
347
|
+
/** Gate (one or many) — when unmet the node is skipped, not failed. */
|
|
348
|
+
when?: ConditionInput;
|
|
349
|
+
/** A failure here does not fail the DAG, and does not block dependents. */
|
|
350
|
+
optional?: boolean;
|
|
351
|
+
/**
|
|
352
|
+
* Run this node in its own git worktree on a fork branch (branches-as-teams).
|
|
353
|
+
* Concurrent writers then never collide on files or the index, and the node's
|
|
354
|
+
* committed work lands back into the parent branch on pass. Defaults to the
|
|
355
|
+
* DAG's `isolation`. Opt-in: forking a worktree has a real setup cost, and a
|
|
356
|
+
* read-only node never needs it.
|
|
357
|
+
*/
|
|
358
|
+
isolate?: boolean;
|
|
359
|
+
/**
|
|
360
|
+
* Timeout inherited by this node's subtree. Agent leaves and agent judges use
|
|
361
|
+
* it unless they set their own timeout.
|
|
362
|
+
*/
|
|
363
|
+
timeoutMs?: number;
|
|
364
|
+
/** Extra hard-timeout window after `timeoutMs` for completed-but-late leaves. */
|
|
365
|
+
timeoutGraceMs?: number;
|
|
366
|
+
/**
|
|
367
|
+
* Restrict which upstream nodes this node may kick work back to. When set, a
|
|
368
|
+
* `kickback` whose `to` is not in this list is rejected (logged, not run); when
|
|
369
|
+
* unset, any ancestor is a valid target. A kickback to a non-ancestor is always
|
|
370
|
+
* rejected. Only consulted when the dag's `maxKickbacks` is set.
|
|
371
|
+
*/
|
|
372
|
+
acceptsKickbackTo?: string[];
|
|
373
|
+
}
|
|
374
|
+
export type KickbackBudget = number | Readonly<Record<string, number>>;
|
|
375
|
+
export interface DagConfig {
|
|
376
|
+
name: string;
|
|
377
|
+
/** Node name → a `DagNode`, or a bare `Job` (shorthand for no deps/gates). */
|
|
378
|
+
nodes: Record<string, DagNode | Job>;
|
|
379
|
+
/** Max nodes running at once. Default: 4. */
|
|
380
|
+
concurrency?: number;
|
|
381
|
+
/** When a required node fails, abort the rest. Default: true. */
|
|
382
|
+
stopOnError?: boolean;
|
|
383
|
+
/**
|
|
384
|
+
* Default isolation for nodes that do not set `isolate`. `'worktree'` runs each
|
|
385
|
+
* such node in its own worktree + fork branch, landed back on pass. Off by
|
|
386
|
+
* default — the shared workspace.
|
|
387
|
+
*/
|
|
388
|
+
isolation?: 'worktree';
|
|
389
|
+
/**
|
|
390
|
+
* Give each ISOLATED node its own environment, brought up when its worktree
|
|
391
|
+
* forks and torn down when it joins — so every branch-team gets its own stage,
|
|
392
|
+
* named by the provider from the workspace branch. Requires isolation; a
|
|
393
|
+
* non-isolated node shares the workspace and gets no per-team env.
|
|
394
|
+
*/
|
|
395
|
+
environment?: Environment;
|
|
396
|
+
/**
|
|
397
|
+
* What to do when an isolated node's land-back conflicts. `'fail'` (default)
|
|
398
|
+
* fails the node. `'synthesize'` runs `mergeSynthesis`: an agent resolves the
|
|
399
|
+
* conflict and writes a synthesised merge body.
|
|
400
|
+
*/
|
|
401
|
+
onConflict?: 'fail' | 'synthesize';
|
|
402
|
+
/**
|
|
403
|
+
* Re-run budget for cross-stage feedback. A number keeps the graph-wide
|
|
404
|
+
* counter. A map gives each target node its own counter. Default 0 means
|
|
405
|
+
* kickbacks are ignored and behaviour is unchanged.
|
|
406
|
+
*/
|
|
407
|
+
maxKickbacks?: KickbackBudget;
|
|
408
|
+
}
|
|
409
|
+
/** Per-node disposition within a DAG run. */
|
|
410
|
+
export type NodePhase = 'start' | 'skip' | 'done';
|
|
411
|
+
export type ProofKind = 'html' | 'image' | 'markdown' | 'table' | 'json';
|
|
412
|
+
export interface ProofArtifact {
|
|
413
|
+
kind: ProofKind;
|
|
414
|
+
title?: string;
|
|
415
|
+
description?: string;
|
|
416
|
+
mediaType?: string;
|
|
417
|
+
meta?: Record<string, string | number | boolean | null>;
|
|
418
|
+
path?: string;
|
|
419
|
+
data?: JsonValue;
|
|
420
|
+
}
|
|
421
|
+
export interface ProofRecord {
|
|
422
|
+
name: string;
|
|
423
|
+
path: string[];
|
|
424
|
+
artifact: ProofArtifact;
|
|
425
|
+
}
|
|
426
|
+
/** The token totals a run reports. */
|
|
427
|
+
export interface UsageTotals {
|
|
428
|
+
readonly inputTokens: number;
|
|
429
|
+
readonly outputTokens: number;
|
|
430
|
+
readonly cacheCreationInputTokens?: number;
|
|
431
|
+
readonly cacheReadInputTokens?: number;
|
|
432
|
+
/** Calls that reported no usage, so a total can say what it is missing. */
|
|
433
|
+
readonly unmeasuredCalls?: number;
|
|
434
|
+
}
|
|
435
|
+
export type ConditionKind = 'start' | 'until' | 'stopOn';
|
|
436
|
+
export type LoopEvent = {
|
|
437
|
+
kind: 'run:start';
|
|
438
|
+
ts: number;
|
|
439
|
+
path: [];
|
|
440
|
+
runId?: string;
|
|
441
|
+
recordPath?: string;
|
|
442
|
+
} | {
|
|
443
|
+
kind: 'run:end';
|
|
444
|
+
ts: number;
|
|
445
|
+
path: [];
|
|
446
|
+
outcome: Outcome;
|
|
447
|
+
usage: UsageTotals;
|
|
448
|
+
runId?: string;
|
|
449
|
+
recordPath?: string;
|
|
450
|
+
} | {
|
|
451
|
+
kind: 'workflow:start';
|
|
452
|
+
ts: number;
|
|
453
|
+
path: string[];
|
|
454
|
+
identity: string;
|
|
455
|
+
workspace: string;
|
|
456
|
+
recordId: string;
|
|
457
|
+
} | {
|
|
458
|
+
kind: 'loop:start';
|
|
459
|
+
ts: number;
|
|
460
|
+
path: string[];
|
|
461
|
+
depth: number;
|
|
462
|
+
max?: number;
|
|
463
|
+
} | {
|
|
464
|
+
kind: 'loop:iteration';
|
|
465
|
+
ts: number;
|
|
466
|
+
path: string[];
|
|
467
|
+
iteration: number;
|
|
468
|
+
} | {
|
|
469
|
+
kind: 'loop:condition';
|
|
470
|
+
ts: number;
|
|
471
|
+
path: string[];
|
|
472
|
+
which: ConditionKind;
|
|
473
|
+
/** The enclosing loop iteration; 0 for start and check-first gates. */
|
|
474
|
+
iteration?: number;
|
|
475
|
+
result: ConditionResult;
|
|
476
|
+
} | {
|
|
477
|
+
/** A Condition executed outside loop start/stop/until control flow. */
|
|
478
|
+
kind: 'condition:result';
|
|
479
|
+
ts: number;
|
|
480
|
+
path: string[];
|
|
481
|
+
label: string;
|
|
482
|
+
iteration: number;
|
|
483
|
+
result: ConditionResult;
|
|
484
|
+
} | {
|
|
485
|
+
kind: 'loop:review';
|
|
486
|
+
ts: number;
|
|
487
|
+
path: string[];
|
|
488
|
+
outcome: Outcome;
|
|
489
|
+
/**
|
|
490
|
+
* Whether the loop will re-enter to act on a failing review (the review's
|
|
491
|
+
* revision was accepted), vs give up because it exhausted its iterations or
|
|
492
|
+
* `maxReviewRestarts`. Mirrors `dag:kickback`'s `accepted`. Only meaningful
|
|
493
|
+
* for a non-pass review; a downstream consumer that omits it (e.g. a test
|
|
494
|
+
* fixture) is treated as accepted.
|
|
495
|
+
*/
|
|
496
|
+
accepted?: boolean;
|
|
497
|
+
} | {
|
|
498
|
+
kind: 'loop:end';
|
|
499
|
+
ts: number;
|
|
500
|
+
path: string[];
|
|
501
|
+
outcome: Outcome;
|
|
502
|
+
iterations: number;
|
|
503
|
+
} | {
|
|
504
|
+
kind: 'loop:stall';
|
|
505
|
+
ts: number;
|
|
506
|
+
path: string[];
|
|
507
|
+
iteration: number;
|
|
508
|
+
report: StallReport;
|
|
509
|
+
} | {
|
|
510
|
+
kind: 'limit:wait';
|
|
511
|
+
ts: number;
|
|
512
|
+
path: string[];
|
|
513
|
+
code: string;
|
|
514
|
+
waitMs: number;
|
|
515
|
+
/** Wall-clock epoch ms the wait ends at (ts + waitMs). */
|
|
516
|
+
resumeAt: number;
|
|
517
|
+
} | {
|
|
518
|
+
kind: 'limit:pause';
|
|
519
|
+
ts: number;
|
|
520
|
+
path: string[];
|
|
521
|
+
code: string;
|
|
522
|
+
reason: string;
|
|
523
|
+
} | {
|
|
524
|
+
kind: 'dag:start';
|
|
525
|
+
ts: number;
|
|
526
|
+
path: string[];
|
|
527
|
+
depth: number;
|
|
528
|
+
nodes: string[];
|
|
529
|
+
} | {
|
|
530
|
+
kind: 'dag:node';
|
|
531
|
+
ts: number;
|
|
532
|
+
path: string[];
|
|
533
|
+
node: string;
|
|
534
|
+
phase: NodePhase;
|
|
535
|
+
/** The node dependencies, normalised to an array when present. */
|
|
536
|
+
needs?: string[];
|
|
537
|
+
/** The node's declared purpose, when present. */
|
|
538
|
+
desc?: string;
|
|
539
|
+
/** The node's declared acceptance criterion, when present. */
|
|
540
|
+
gate?: string;
|
|
541
|
+
/**
|
|
542
|
+
* What the node produced, on a done or a skip. One attempt can emit this
|
|
543
|
+
* twice: a gate waiting for a person records its pause before it waits, so
|
|
544
|
+
* a live consumer sees a paused outcome and then, if an answer arrives, the
|
|
545
|
+
* answered one. A record keeps the latest outcome emitted for the node, so
|
|
546
|
+
* when a waiting run is killed the stored outcome is the pause itself,
|
|
547
|
+
* which is what lets a resumed run return that same still-pending question
|
|
548
|
+
* rather than treating the stage as interrupted and asking a person to
|
|
549
|
+
* reconcile it.
|
|
550
|
+
*/
|
|
551
|
+
outcome?: Outcome;
|
|
552
|
+
/** Soft timeout in force for this node, when one is configured. */
|
|
553
|
+
timeoutMs?: number;
|
|
554
|
+
/**
|
|
555
|
+
* Which run of this node this is: 1 on the first pass, incremented each time
|
|
556
|
+
* a kickback re-runs it. Lets a records consumer tell a re-run's completion
|
|
557
|
+
* from the original and correlate it with the revision that caused it.
|
|
558
|
+
*/
|
|
559
|
+
attempt?: number;
|
|
560
|
+
} | {
|
|
561
|
+
kind: 'dag:end';
|
|
562
|
+
ts: number;
|
|
563
|
+
path: string[];
|
|
564
|
+
outcome: Outcome;
|
|
565
|
+
}
|
|
566
|
+
/** The run's monitor page is up at `url`; emitted once, before the job starts. */
|
|
567
|
+
| {
|
|
568
|
+
kind: 'monitor';
|
|
569
|
+
ts: number;
|
|
570
|
+
path: string[];
|
|
571
|
+
url: string;
|
|
572
|
+
} | {
|
|
573
|
+
kind: 'dag:kickback';
|
|
574
|
+
ts: number;
|
|
575
|
+
path: string[];
|
|
576
|
+
from: string;
|
|
577
|
+
to: string;
|
|
578
|
+
reason: string;
|
|
579
|
+
accepted: boolean;
|
|
580
|
+
/** The one-based request count for this target in this DAG run. */
|
|
581
|
+
count: number;
|
|
582
|
+
/** The configured limit for this target, including the numeric form. */
|
|
583
|
+
limit: number;
|
|
584
|
+
note?: string;
|
|
585
|
+
} | {
|
|
586
|
+
kind: 'job:start';
|
|
587
|
+
ts: number;
|
|
588
|
+
path: string[];
|
|
589
|
+
label: string;
|
|
590
|
+
/** Soft timeout in force for this job, when one is configured. */
|
|
591
|
+
timeoutMs?: number;
|
|
592
|
+
} | {
|
|
593
|
+
kind: 'advisor:consult';
|
|
594
|
+
ts: number;
|
|
595
|
+
path: string[];
|
|
596
|
+
label: string;
|
|
597
|
+
call: number;
|
|
598
|
+
question: string;
|
|
599
|
+
reply: string;
|
|
600
|
+
model?: string;
|
|
601
|
+
} | {
|
|
602
|
+
kind: 'proof';
|
|
603
|
+
ts: number;
|
|
604
|
+
path: string[];
|
|
605
|
+
name: string;
|
|
606
|
+
artifact: ProofArtifact;
|
|
607
|
+
} | {
|
|
608
|
+
kind: 'job:end';
|
|
609
|
+
ts: number;
|
|
610
|
+
path: string[];
|
|
611
|
+
label: string;
|
|
612
|
+
outcome: Outcome;
|
|
613
|
+
} | {
|
|
614
|
+
kind: 'engine:text';
|
|
615
|
+
ts: number;
|
|
616
|
+
path: string[];
|
|
617
|
+
delta: string;
|
|
618
|
+
} | {
|
|
619
|
+
kind: 'engine:thinking';
|
|
620
|
+
ts: number;
|
|
621
|
+
path: string[];
|
|
622
|
+
delta: string;
|
|
623
|
+
} | {
|
|
624
|
+
kind: 'engine:tool';
|
|
625
|
+
ts: number;
|
|
626
|
+
path: string[];
|
|
627
|
+
name: string;
|
|
628
|
+
phase: 'use' | 'result';
|
|
629
|
+
} | {
|
|
630
|
+
kind: 'engine:usage';
|
|
631
|
+
ts: number;
|
|
632
|
+
path: string[];
|
|
633
|
+
model: string;
|
|
634
|
+
usage: UsageReceipt;
|
|
635
|
+
role?: 'writer' | 'reviewer';
|
|
636
|
+
stage?: string;
|
|
637
|
+
} | {
|
|
638
|
+
kind: 'log';
|
|
639
|
+
ts: number;
|
|
640
|
+
path: string[];
|
|
641
|
+
level: LogLevel;
|
|
642
|
+
message: string;
|
|
643
|
+
} | {
|
|
644
|
+
kind: 'error';
|
|
645
|
+
ts: number;
|
|
646
|
+
path: string[];
|
|
647
|
+
message: string;
|
|
648
|
+
code: string;
|
|
649
|
+
};
|
|
650
|
+
export type LoopEventKind = LoopEvent['kind'];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DEFAULT_OWNED_COMMAND_LIMITS, OwnedCommandError, attemptEnvironment, ownedCommandIdentity, resolveCommandExecutable, runOwnedCommand, type OwnedCommandErrorCode, type OwnedCommandObserver, type OwnedCommandRequest, type OwnedCommandResult, } from '@obversa/core/command';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { assertEngineConformance, runEngineConformance, type EngineConformanceFailure, type EngineConformanceFixture, type EngineConformanceReport, type EngineConformanceScenario, } from '@obversa/api/testing';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Thin compatibility surface while the runtime package keeps its old name. */
|
|
2
|
+
export { CLAUDE_SUBAGENT_TOOLS, EngineError, EngineIncompleteResultError, SUBAGENT_TOOLS, isEngine, type AgentRequest, type AgentResult, type AgentResultPart, type AttemptMetadata, type Engine, type EngineEventSink, type EngineFailureKind, type EngineIncompleteResultEvidence, type EngineRef, type EngineSelectionRecord, type EngineStreamEvent, type EngineTransportFailure, type Usage, type UsageReceipt, } from '@obversa/api';
|
|
3
|
+
export { attemptEnvironment, attemptEnvironment as requestEnv } from '@obversa/core/command';
|
|
4
|
+
export type { EngineName } from '@obversa/api';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { EngineError, EngineIncompleteResultError, LANE_DEAD_FAILURES, classifyEngineFailure, type EngineErrorInit, type EngineFailureKind, } from '@obversa/api';
|