@noetaris/harness 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 NOETARIS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,219 @@
1
+ declare const _fieldType: unique symbol;
2
+ type FieldDefinition<T> = {
3
+ readonly [_fieldType]: T;
4
+ readonly default?: () => T;
5
+ readonly reduce?: (current: T, update: T) => T;
6
+ };
7
+ type StateFromSchema<S> = {
8
+ [K in keyof S]: S[K] extends FieldDefinition<infer T> ? T : never;
9
+ };
10
+ type FieldOptions<T> = {
11
+ default?: () => T;
12
+ reduce?: (current: T, update: T) => T;
13
+ };
14
+ declare function field<T>(options?: FieldOptions<T>): FieldDefinition<T>;
15
+
16
+ declare const REQUIRED_TAG: "__noetaris_required__";
17
+ declare const RUNTIME_TAG: "__noetaris_runtime__";
18
+ type RequiredMarker = {
19
+ readonly _tag: typeof REQUIRED_TAG;
20
+ };
21
+ type RuntimeMarker = {
22
+ readonly _tag: typeof RUNTIME_TAG;
23
+ };
24
+ type DeepWithMarkers<T> = T extends (...args: any[]) => any ? T | RequiredMarker | RuntimeMarker : T extends object ? {
25
+ [K in keyof T]: DeepWithMarkers<T[K]> | RequiredMarker | RuntimeMarker;
26
+ } | RequiredMarker | RuntimeMarker : T | RequiredMarker | RuntimeMarker;
27
+ declare function required(): RequiredMarker;
28
+ declare function runtime(): RuntimeMarker;
29
+ declare function isRequiredMarker(value: unknown): value is RequiredMarker;
30
+ declare function isRuntimeMarker(value: unknown): value is RuntimeMarker;
31
+
32
+ /** Framework fields injected into every step function's state argument. */
33
+ interface FrameworkState {
34
+ readonly $error: Error | null;
35
+ readonly $interrupt: {
36
+ readonly interruptId: string;
37
+ readonly prompt: unknown;
38
+ readonly response?: unknown;
39
+ } | null;
40
+ }
41
+ /** The full state type visible inside step functions: user state + framework fields. */
42
+ type StepState<S> = S & FrameworkState;
43
+ /**
44
+ * State transformer. Receives the full step state and ctx; returns a partial update
45
+ * of user-defined state fields only (framework-reserved fields are excluded from return).
46
+ * May be async.
47
+ */
48
+ type RunFn<S, Ctx> = (state: StepState<S>, ctx: Ctx & {
49
+ readonly sessionId: string;
50
+ readonly interrupt: (prompt: unknown, id?: string) => Promise<unknown>;
51
+ readonly emit: (name: string, payload?: unknown) => void;
52
+ }) => Promise<Partial<Omit<S, '$error' | '$interrupt'>>> | Partial<Omit<S, '$error' | '$interrupt'>>;
53
+ /**
54
+ * Pure signal emitter. Receives step state without $error — the route is not called on the error
55
+ * path unless the step opts in via `optin: '$error'` in the step config. Synchronous by design.
56
+ * No ctx — pure read only.
57
+ */
58
+ type RouteFn<S> = (state: S & Omit<FrameworkState, '$error'>) => string;
59
+ /**
60
+ * Pure signal emitter for error-aware steps (optin: '$error').
61
+ * Receives the full step state including $error — the executor calls this on both the success
62
+ * and error paths. Declare `optin: '$error'` in the step config to use this variant.
63
+ */
64
+ type ErrorAwareRouteFn<S> = (state: StepState<S>) => string;
65
+ /**
66
+ * Options passed to `.step(name, options)`. At least one of `run` or `route` must be set.
67
+ *
68
+ * Two variants:
69
+ * - Without `optin`: route receives state without `$error` (TypeScript-enforced). On error,
70
+ * the framework falls through to `l.onError()` or pauses with `signal: "$error"` automatically.
71
+ * - With `optin: '$error'`: route receives the full state including `$error`, and the executor
72
+ * calls route on the error path. Use this when the step's route needs to inspect or handle errors.
73
+ */
74
+ type StepOptions<S, Ctx> = {
75
+ readonly optin: '$error';
76
+ run?: RunFn<S, Ctx>;
77
+ route?: ErrorAwareRouteFn<S>;
78
+ } | {
79
+ readonly optin?: undefined;
80
+ run?: RunFn<S, Ctx>;
81
+ route?: RouteFn<S>;
82
+ };
83
+ /**
84
+ * Fluent chain returned by .on(signal). Caller must call either .to(step) or .end()
85
+ * to complete the transition declaration.
86
+ */
87
+ interface OnChain<S, Ctx> {
88
+ /** Route the signal to the named step. Returns the builder for further chaining. */
89
+ to(step: string): LoopBuilder<S, Ctx>;
90
+ /** Exit the loop when this signal is emitted. Returns the builder for further chaining. */
91
+ end(): LoopBuilder<S, Ctx>;
92
+ }
93
+ /**
94
+ * The DSL object passed as `l` to the h.loop(l => ...) builder lambda.
95
+ * All methods mutate internal state and return `this` for chaining.
96
+ */
97
+ interface LoopBuilder<S, Ctx> {
98
+ /** Mark the loop as having a declared entry. The first .step() called after .start() is the entry. */
99
+ start(): LoopBuilder<S, Ctx>;
100
+ /** Declare a step. At least one of run or route must be set (validated later by LoopValidator). */
101
+ step(name: string, options: StepOptions<S, Ctx>): LoopBuilder<S, Ctx>;
102
+ /**
103
+ * Begin a signal transition declaration. Must be called immediately after .step() or after
104
+ * a previous .on().to() or .on().end() chain (attaches to the most recently declared step).
105
+ */
106
+ on(signal: string): OnChain<S, Ctx>;
107
+ /**
108
+ * Declare an explicit unconditional next target for the most recently declared step.
109
+ * Mutually exclusive with route (validated by LoopValidator).
110
+ */
111
+ next(name: string): LoopBuilder<S, Ctx>;
112
+ /**
113
+ * Declare a loop-level fallback error step. Any step whose run throws and whose
114
+ * route does not handle $error will route to this step instead of pausing the session.
115
+ * Only one onError target per loop; multiple calls replace the previous target (last wins).
116
+ */
117
+ onError(step: string): LoopBuilder<S, Ctx>;
118
+ }
119
+
120
+ type Harness<Ctx, State, Req extends keyof Ctx = never, Run extends keyof Ctx = never> = {
121
+ provide<K extends keyof Ctx>(key: K, value: RequiredMarker): Harness<Ctx, State, Req | K, Run>;
122
+ provide<K extends keyof Ctx>(key: K, value: RuntimeMarker): Harness<Ctx, State, Req, Run | K>;
123
+ provide<K extends keyof Ctx>(key: K, value: DeepWithMarkers<Ctx[K]>): Harness<Ctx, State, Req, Run>;
124
+ store(stores: DeepWithMarkers<{
125
+ session?: unknown;
126
+ } & Record<string, unknown>>): Harness<Ctx, State, Req, Run>;
127
+ loop(builder: (l: LoopBuilder<State, Ctx>) => void): Harness<Ctx, State, Req, Run>;
128
+ };
129
+ declare function createHarness<Ctx = any>(): <S extends object = {}>(// any: allows createHarness() without an explicit Ctx type parameter
130
+ stateSchema?: S) => Harness<Ctx, StateFromSchema<S>>;
131
+
132
+ interface SessionStore {
133
+ load(sessionId: string): Promise<StoredRun | null>;
134
+ save(sessionId: string, run: StoredRun): Promise<void>;
135
+ loadHistory?(sessionId: string): Promise<StoredRun[]>;
136
+ branch?(sessionId: string, runId: string): Promise<string>;
137
+ }
138
+ interface StoredRun {
139
+ readonly runId: string;
140
+ readonly sessionId: string;
141
+ readonly startedAt: string;
142
+ readonly settledAt: string;
143
+ readonly phase: 'paused' | 'completed';
144
+ readonly initialState: Record<string, unknown>;
145
+ readonly finalState: Record<string, unknown>;
146
+ readonly signal?: string;
147
+ readonly step?: string;
148
+ }
149
+ type SessionPhase = {
150
+ readonly phase: 'fresh';
151
+ } | {
152
+ readonly phase: 'in-flight';
153
+ readonly step: null;
154
+ } | {
155
+ readonly phase: 'paused';
156
+ readonly signal?: string;
157
+ readonly step: string;
158
+ } | {
159
+ readonly phase: 'completed';
160
+ readonly signal?: string;
161
+ };
162
+
163
+ interface RunOutcome {
164
+ readonly state: Record<string, unknown>;
165
+ readonly signal: string | null;
166
+ }
167
+ interface RunHandle extends PromiseLike<RunOutcome> {
168
+ /** Cancel the in-flight run at the next safe point between steps. Idempotent. */
169
+ stop(): void;
170
+ /**
171
+ * Provide a response to a pending ctx.interrupt() call.
172
+ * Returns a new RunHandle for the resumed execution.
173
+ * Stub in F8 — implemented in F9.
174
+ */
175
+ resume(response: unknown, interruptId: string): RunHandle;
176
+ /** The session identity for this run. */
177
+ readonly sessionId: string;
178
+ /**
179
+ * The name of the step currently executing in this process.
180
+ * null before the first step runs, after execution settles, and always when inspected cross-process.
181
+ */
182
+ readonly currentStep: string | null;
183
+ }
184
+
185
+ interface Agent {
186
+ /**
187
+ * Start a new run. Returns a RunHandle synchronously before execution begins.
188
+ */
189
+ run(initialState: Record<string, unknown>, resources: Record<string, unknown>): RunHandle;
190
+ /**
191
+ * Cross-process entry point for responding to a pending interrupt.
192
+ * Returns a RunHandle synchronously; the execution promise performs the resume.
193
+ */
194
+ resume(response: unknown, sessionId: string, interruptId: string): RunHandle;
195
+ /**
196
+ * Query the session store for the current phase of a session.
197
+ */
198
+ status(sessionId: string): Promise<SessionPhase>;
199
+ }
200
+ declare function createAgent<Ctx, State, Req extends keyof Ctx, Run extends keyof Ctx>(h: Harness<Ctx, State, Req, Run>, slots: Pick<Ctx, Req>): Agent;
201
+
202
+ declare class NoInterruptError extends Error {
203
+ constructor();
204
+ }
205
+
206
+ declare class SessionInFlightError extends Error {
207
+ readonly sessionId: string;
208
+ constructor(sessionId: string);
209
+ }
210
+ declare class SessionPendingInterruptError extends Error {
211
+ readonly sessionId: string;
212
+ constructor(sessionId: string);
213
+ }
214
+ declare class StoreLoadError extends Error {
215
+ readonly cause: unknown;
216
+ constructor(cause: unknown);
217
+ }
218
+
219
+ export { type Agent, type DeepWithMarkers, type FieldDefinition, type Harness, NoInterruptError, REQUIRED_TAG, RUNTIME_TAG, type RequiredMarker, type RuntimeMarker, SessionInFlightError, SessionPendingInterruptError, type SessionStore, type StateFromSchema, StoreLoadError, type StoredRun, createAgent, createHarness, field, isRequiredMarker, isRuntimeMarker, required, runtime };