@obversa/runtime 0.2.0 → 0.2.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.
- package/dist/api.d.ts +1 -0
- package/dist/api.js +260 -71
- package/dist/api.js.map +1 -1
- package/dist/{chunk-ZXVFWRW5.js → chunk-G2OIKOUI.js} +273 -64
- package/dist/chunk-G2OIKOUI.js.map +1 -0
- package/dist/core/interaction.d.ts +26 -0
- package/dist/core/job.d.ts +3 -0
- package/dist/core/judge.d.ts +36 -0
- package/dist/core/types.d.ts +40 -1
- package/dist/runtime/persist.d.ts +4 -17
- package/dist/workflow-support.js +1 -1
- package/dist/workflow.d.ts +15 -1
- package/package.json +4 -4
- package/dist/chunk-ZXVFWRW5.js.map +0 -1
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type JsonObject, type JsonValue } from '../graph/value.js';
|
|
2
|
+
import type { InteractionBinding, InteractionResponse, Job, JobContext, Outcome } from './types.js';
|
|
3
|
+
export type { InteractionBinding, InteractionResponse } from './types.js';
|
|
4
|
+
/** Keep declared job identity separate from an engine's mutable call state. */
|
|
5
|
+
export declare function interactionDeclaration<T extends Function>(job: T, declaration: unknown): T;
|
|
6
|
+
export declare function interactionIdentity(value: unknown): string;
|
|
7
|
+
export declare function jsonSnapshot(value: unknown): JsonObject;
|
|
8
|
+
/** Save only JSON outcome fields; runtime Error objects are not continuation data. */
|
|
9
|
+
export declare function outcomeSnapshot(outcome: Outcome): JsonObject;
|
|
10
|
+
export declare function savedInteraction(ctx: JobContext, path: readonly string[], identity: string): JsonObject | undefined;
|
|
11
|
+
export declare function hasSavedInteraction(ctx: JobContext, path: readonly string[]): boolean;
|
|
12
|
+
export declare function checkpointInteraction(ctx: JobContext, path: readonly string[], identity: string, data: JsonObject | null): void;
|
|
13
|
+
export declare function interactionResponse(value: unknown): InteractionResponse;
|
|
14
|
+
export declare const DEFAULT_INTERACTION: InteractionBinding;
|
|
15
|
+
export declare function requestInteraction(binding: InteractionBinding, question: string, input: JsonObject, ctx: JobContext, humanApproval?: boolean): Promise<{
|
|
16
|
+
response: InteractionResponse;
|
|
17
|
+
} | {
|
|
18
|
+
paused: Outcome;
|
|
19
|
+
}>;
|
|
20
|
+
export interface HumanReviewOptions {
|
|
21
|
+
readonly question: string;
|
|
22
|
+
readonly input: JsonValue | ((ctx: JobContext) => JsonValue | Promise<JsonValue>);
|
|
23
|
+
readonly interaction: InteractionBinding;
|
|
24
|
+
}
|
|
25
|
+
/** A person's explicit approval is the only passing result of this review. */
|
|
26
|
+
export declare function humanReview(name: string, options: HumanReviewOptions): Job;
|
package/dist/core/job.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* an HTTP API, or any framework — swap the engine and the same job runs.
|
|
6
6
|
*/
|
|
7
7
|
import type { Outcome, Job, JobContext, ProofArtifact } from './types.js';
|
|
8
|
+
import { type InteractionBinding } from './interaction.js';
|
|
8
9
|
/** Shared state key holding every engine answer the run recorded so far.
|
|
9
10
|
* Written by the runtime beside each engine:usage event; read by workflow
|
|
10
11
|
* layers that must compare what answered against what was declared. */
|
|
@@ -26,6 +27,8 @@ import { type LoopErrorCode } from './errors.js';
|
|
|
26
27
|
import { type AgentDef } from './agent.js';
|
|
27
28
|
import { kickback, revisionRequest } from './feedback.js';
|
|
28
29
|
export interface AgentJobConfig {
|
|
30
|
+
/** Opt in to requesting a rich interaction and continuing this same model. */
|
|
31
|
+
readonly interaction?: InteractionBinding;
|
|
29
32
|
/** Tag this job's engine answers with the review side and stage they
|
|
30
33
|
* belong to, so a workflow layer can compare recorded sides without
|
|
31
34
|
* inferring them from paths. Untagged answers are outside such gates. */
|
package/dist/core/judge.d.ts
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* The default question set and the routing rule below make a loop that was
|
|
8
8
|
* first hand-wired as dag nodes into a runtime primitive.
|
|
9
9
|
*/
|
|
10
|
+
import { type InteractionBinding, type InteractionResponse } from './interaction.js';
|
|
11
|
+
import type { Outcome } from './types.js';
|
|
10
12
|
import type { FeedbackActionSeverity, FeedbackFinding, Judge, JudgeAnswer, JudgeQuestions, JobContext } from './types.js';
|
|
11
13
|
export type { Judge, JudgeAnswer, JudgeQuestion, JudgeQuestions } from './types.js';
|
|
12
14
|
/**
|
|
@@ -25,6 +27,7 @@ export declare function stopQuestions(what?: string): JudgeQuestions;
|
|
|
25
27
|
export declare function judge(seat: Judge['seat'], opts: {
|
|
26
28
|
cap: number;
|
|
27
29
|
questions?: JudgeQuestions;
|
|
30
|
+
interaction?: InteractionBinding;
|
|
28
31
|
}): Judge;
|
|
29
32
|
export declare function isJudge(value: unknown): value is Judge;
|
|
30
33
|
/** A finding tagged block, by any reviewer, in this round's findings. */
|
|
@@ -41,6 +44,7 @@ export interface JudgeRound {
|
|
|
41
44
|
}
|
|
42
45
|
/** What the judge sees. `cap` rides along so it can reason about how much room is left. */
|
|
43
46
|
export interface JudgeState {
|
|
47
|
+
readonly productFeedback?: readonly InteractionResponse[];
|
|
44
48
|
readonly useCase?: string;
|
|
45
49
|
/** The file being refined, relative to the workspace, when there is one. */
|
|
46
50
|
readonly file?: string;
|
|
@@ -56,6 +60,15 @@ export interface JudgeDecision {
|
|
|
56
60
|
readonly again: boolean;
|
|
57
61
|
/** Always names the judge, so a kickback or a stop is traceable to it. */
|
|
58
62
|
readonly reason: string;
|
|
63
|
+
/**
|
|
64
|
+
* What a stop means, when `again` is false. `ship`: the work holds as it
|
|
65
|
+
* is, so it stands as a pass carrying the judge's reason. `fail`: another
|
|
66
|
+
* round will not fix it, so the run stops there and the requesting side's
|
|
67
|
+
* own failure stands. `product_decision` asks a person, then returns the
|
|
68
|
+
* feedback to this judge without advancing the review round. Absent when
|
|
69
|
+
* `again` is true.
|
|
70
|
+
*/
|
|
71
|
+
readonly stop?: 'ship' | 'fail' | 'product_decision';
|
|
59
72
|
}
|
|
60
73
|
/**
|
|
61
74
|
* Route on the judge's answers. The chosen `stop_reason` is read first: in
|
|
@@ -66,6 +79,17 @@ export interface JudgeDecision {
|
|
|
66
79
|
* here: the caller enforces the cap itself (a loop's own `maxReviewRestarts`,
|
|
67
80
|
* or a dag's own kickback budget), and a block finding never reaches this
|
|
68
81
|
* function, it always goes back without asking the judge.
|
|
82
|
+
*
|
|
83
|
+
* `product_decision` pauses for rich feedback and another judgment of the
|
|
84
|
+
* same work. For a terminal stop, `holds` and `over_polishing` say the work
|
|
85
|
+
* is good enough as it stands, so it ships as a pass; other stops, including
|
|
86
|
+
* `not_converging` and any choice a custom question set invents of its own,
|
|
87
|
+
* says the run should not ship silently, so it stops there and the
|
|
88
|
+
* requesting side's failure stands. The same split applies to the
|
|
89
|
+
* probability fallback: a clear `holds` or a clear "not worth doing" ships
|
|
90
|
+
* the work; an unclear "another round is not worth it" fails instead of
|
|
91
|
+
* shipping, since its own criteria already blend polish with a stall and
|
|
92
|
+
* cannot tell the two apart.
|
|
69
93
|
*/
|
|
70
94
|
export declare function judgeDecision(answers: Readonly<Record<string, JudgeAnswer>>): JudgeDecision;
|
|
71
95
|
/**
|
|
@@ -81,3 +105,15 @@ export declare function askJudge(cfg: Judge, state: JudgeState, ctx: JobContext,
|
|
|
81
105
|
answers: Readonly<Record<string, JudgeAnswer>>;
|
|
82
106
|
decision: JudgeDecision;
|
|
83
107
|
}>;
|
|
108
|
+
/** Resume only the deliberate product question; every returned answer goes back to this judge. */
|
|
109
|
+
export declare function consultJudge(cfg: Judge, initialState: JudgeState, ctx: JobContext, path: readonly string[], options: {
|
|
110
|
+
readonly identity: string;
|
|
111
|
+
readonly pending: boolean;
|
|
112
|
+
readonly save: (state: JudgeState) => void;
|
|
113
|
+
}): Promise<{
|
|
114
|
+
state: JudgeState;
|
|
115
|
+
decision: JudgeDecision;
|
|
116
|
+
} | {
|
|
117
|
+
state: JudgeState;
|
|
118
|
+
paused: Outcome;
|
|
119
|
+
}>;
|
package/dist/core/types.d.ts
CHANGED
|
@@ -18,9 +18,20 @@ import type { Engine, EngineRef, UsageReceipt } from '../engines/engine.js';
|
|
|
18
18
|
import type { Memory, TeamSeat } from '@obversa/api';
|
|
19
19
|
import type { LoopError } from './errors.js';
|
|
20
20
|
import type { EnvHandle, Environment } from '../env/environment.js';
|
|
21
|
-
import type { JsonValue, RunBrief } from '../graph/value.js';
|
|
21
|
+
import type { JsonObject, JsonValue, RunBrief } from '../graph/value.js';
|
|
22
22
|
import type { CallbackEvent, ClaimResult, ReleaseResult, SubmitResult } from '../callback/client.js';
|
|
23
23
|
import type { CallbackRequest } from '../callback/gate.js';
|
|
24
|
+
export type InteractionResponse = JsonObject & {
|
|
25
|
+
readonly feedback: JsonValue;
|
|
26
|
+
readonly prompt: string;
|
|
27
|
+
readonly decision?: 'approved' | 'changes-requested';
|
|
28
|
+
};
|
|
29
|
+
export interface InteractionBinding {
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly responseSchema: JsonObject;
|
|
32
|
+
/** Return no answer when the page is cancelled, interrupted or times out. */
|
|
33
|
+
readonly answer?: (request: CallbackRequest, signal: AbortSignal) => Promise<InteractionResponse | undefined>;
|
|
34
|
+
}
|
|
24
35
|
/**
|
|
25
36
|
* The client a run's questions go through: the in-memory `CallbackClient`,
|
|
26
37
|
* or the stored client (`createStoredCallbackClient`) whose questions survive
|
|
@@ -79,6 +90,26 @@ export interface Outcome {
|
|
|
79
90
|
*/
|
|
80
91
|
revision?: RevisionRequest;
|
|
81
92
|
}
|
|
93
|
+
export type RecordedStage = {
|
|
94
|
+
readonly kind: 'interrupted';
|
|
95
|
+
readonly startLine: number;
|
|
96
|
+
} | {
|
|
97
|
+
readonly kind: 'completed';
|
|
98
|
+
readonly outcome: Outcome;
|
|
99
|
+
};
|
|
100
|
+
export interface ResumedStageRecords {
|
|
101
|
+
readonly interactions: ReadonlyMap<string, {
|
|
102
|
+
identity: string;
|
|
103
|
+
workspace: string;
|
|
104
|
+
data: JsonObject;
|
|
105
|
+
}>;
|
|
106
|
+
readonly anchors: ReadonlyMap<string, {
|
|
107
|
+
readonly identity: string;
|
|
108
|
+
readonly workspace: string;
|
|
109
|
+
readonly recordId: string;
|
|
110
|
+
}>;
|
|
111
|
+
readonly stages: ReadonlyMap<string, RecordedStage>;
|
|
112
|
+
}
|
|
82
113
|
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
83
114
|
/**
|
|
84
115
|
* Where a job's code lives: a working directory and (when it is a git repo) the
|
|
@@ -413,6 +444,7 @@ export interface Judge {
|
|
|
413
444
|
readonly seat: TeamSeat;
|
|
414
445
|
readonly cap: number;
|
|
415
446
|
readonly questions: JudgeQuestions;
|
|
447
|
+
readonly interaction?: InteractionBinding;
|
|
416
448
|
}
|
|
417
449
|
export type KickbackBudget = number | Readonly<Record<string, number | Judge>>;
|
|
418
450
|
export interface DagConfig {
|
|
@@ -685,6 +717,13 @@ export type LoopEvent = {
|
|
|
685
717
|
path: string[];
|
|
686
718
|
answers: Readonly<Record<string, JudgeAnswer>>;
|
|
687
719
|
reason: string;
|
|
720
|
+
} | {
|
|
721
|
+
kind: 'interaction:checkpoint';
|
|
722
|
+
ts: number;
|
|
723
|
+
path: string[];
|
|
724
|
+
identity: string;
|
|
725
|
+
workspace: string;
|
|
726
|
+
data: import('../graph/value.js').JsonObject | null;
|
|
688
727
|
} | {
|
|
689
728
|
kind: 'log';
|
|
690
729
|
ts: number;
|
|
@@ -1,31 +1,18 @@
|
|
|
1
|
+
import type { UsageReceipt } from '../engines/engine.js';
|
|
1
2
|
import type { RecordedEngineUsage } from '../core/job.js';
|
|
2
|
-
import type { LoopEvent,
|
|
3
|
+
import type { LoopEvent, ResumedStageRecords } from '../core/types.js';
|
|
4
|
+
export type { RecordedStage, ResumedStageRecords } from '../core/types.js';
|
|
3
5
|
interface RecorderOptions {
|
|
4
6
|
thin?: boolean;
|
|
5
7
|
/** Append to the existing record instead of truncating it. */
|
|
6
8
|
resume?: boolean;
|
|
7
9
|
}
|
|
8
|
-
export type RecordedStage = {
|
|
9
|
-
readonly kind: 'interrupted';
|
|
10
|
-
readonly startLine: number;
|
|
11
|
-
} | {
|
|
12
|
-
readonly kind: 'completed';
|
|
13
|
-
readonly outcome: Outcome;
|
|
14
|
-
};
|
|
15
|
-
export interface ResumedStageRecords {
|
|
16
|
-
readonly anchors: ReadonlyMap<string, {
|
|
17
|
-
readonly identity: string;
|
|
18
|
-
readonly workspace: string;
|
|
19
|
-
readonly recordId: string;
|
|
20
|
-
}>;
|
|
21
|
-
readonly stages: ReadonlyMap<string, RecordedStage>;
|
|
22
|
-
}
|
|
23
10
|
/** Read the latest stage state and the engine answers recorded for each stage.
|
|
24
11
|
* A missing record has no stage state, so resume starts fresh. */
|
|
25
12
|
export declare function readResumeRecord(path: string): {
|
|
13
|
+
readonly receipts: readonly UsageReceipt[];
|
|
26
14
|
readonly outcomes: ResumedStageRecords;
|
|
27
15
|
readonly usage: ReadonlyMap<string, readonly RecordedEngineUsage[]>;
|
|
28
16
|
};
|
|
29
17
|
/** Append every durable event as one JSON line. */
|
|
30
18
|
export declare function makeRecorder(path: string, options?: RecorderOptions): (event: LoopEvent) => void;
|
|
31
|
-
export {};
|
package/dist/workflow-support.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { INVALID_TEAM_DECISION, assertDistinctSeats, outcomeFromAgentText, panelReviewers, requireNoFiles, requireNonEmptyFiles, seatIdentity, teamAgent } from './chunk-
|
|
1
|
+
export { INVALID_TEAM_DECISION, assertDistinctSeats, outcomeFromAgentText, panelReviewers, requireNoFiles, requireNonEmptyFiles, seatIdentity, teamAgent } from './chunk-G2OIKOUI.js';
|
|
2
2
|
import './chunk-NIBHM5I5.js';
|
|
3
3
|
import './chunk-5GLEABOU.js';
|
|
4
4
|
//# sourceMappingURL=workflow-support.js.map
|
package/dist/workflow.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type TeamSeat } from '@obversa/api';
|
|
2
2
|
import type { Job, Judge, Outcome, ConditionInput } from './core/types.js';
|
|
3
|
+
import { type InteractionBinding } from './core/interaction.js';
|
|
3
4
|
export interface BriefSource {
|
|
4
5
|
readonly brief: string;
|
|
5
6
|
readonly files?: readonly string[];
|
|
@@ -7,6 +8,7 @@ export interface BriefSource {
|
|
|
7
8
|
export interface PersonRole {
|
|
8
9
|
readonly kind: 'person';
|
|
9
10
|
readonly question: string;
|
|
11
|
+
readonly interaction?: InteractionBinding;
|
|
10
12
|
}
|
|
11
13
|
export type WorkflowRole = TeamSeat | readonly TeamSeat[] | PersonRole;
|
|
12
14
|
export interface WorkflowStageBase {
|
|
@@ -33,22 +35,32 @@ export type WorkflowStage = WorkflowStageBase & {} & ({
|
|
|
33
35
|
readonly run?: never;
|
|
34
36
|
readonly panel?: never;
|
|
35
37
|
readonly input?: never;
|
|
38
|
+
readonly fn?: never;
|
|
36
39
|
} | {
|
|
37
40
|
readonly run: string | readonly string[];
|
|
38
41
|
readonly agent?: never;
|
|
39
42
|
readonly panel?: never;
|
|
40
43
|
readonly input?: never;
|
|
44
|
+
readonly fn?: never;
|
|
41
45
|
} | {
|
|
42
46
|
readonly panel: string;
|
|
43
47
|
readonly agree?: number;
|
|
44
48
|
readonly agent?: never;
|
|
45
49
|
readonly run?: never;
|
|
46
50
|
readonly input?: never;
|
|
51
|
+
readonly fn?: never;
|
|
47
52
|
} | {
|
|
48
53
|
readonly input: string;
|
|
49
54
|
readonly agent?: never;
|
|
50
55
|
readonly run?: never;
|
|
51
56
|
readonly panel?: never;
|
|
57
|
+
readonly fn?: never;
|
|
58
|
+
} | {
|
|
59
|
+
readonly fn: Job;
|
|
60
|
+
readonly agent?: never;
|
|
61
|
+
readonly run?: never;
|
|
62
|
+
readonly panel?: never;
|
|
63
|
+
readonly input?: never;
|
|
52
64
|
});
|
|
53
65
|
export interface NamedStage {
|
|
54
66
|
readonly name: string;
|
|
@@ -74,6 +86,8 @@ export interface WorkflowConfig {
|
|
|
74
86
|
};
|
|
75
87
|
}
|
|
76
88
|
export declare function briefFromFile(path: string | URL): BriefSource;
|
|
77
|
-
export declare function person(question: string
|
|
89
|
+
export declare function person(question: string, options?: {
|
|
90
|
+
interaction?: InteractionBinding;
|
|
91
|
+
}): PersonRole;
|
|
78
92
|
export declare function stage(name: string, config: WorkflowStage): NamedStage;
|
|
79
93
|
export declare function workflow(name: string, config: WorkflowConfig): Job;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@obversa/runtime",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Model how your team really works. Durable agent workflows, reviews that send work back, no database needed.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Jonny Neill <jonnyneill@hotmail.com>",
|
|
@@ -51,16 +51,16 @@
|
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"p-limit": "^7.3.0",
|
|
53
53
|
"toposort": "^2.0.2",
|
|
54
|
-
"@obversa/core": "0.2.
|
|
54
|
+
"@obversa/core": "0.2.1"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"@obversa/api": "0.2.
|
|
57
|
+
"@obversa/api": "0.2.1"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@types/node": "22.12.0",
|
|
61
61
|
"@types/toposort": "^2.0.7",
|
|
62
62
|
"execa": "^9.6.1",
|
|
63
|
-
"@obversa/api": "0.2.
|
|
63
|
+
"@obversa/api": "0.2.1"
|
|
64
64
|
},
|
|
65
65
|
"keywords": [
|
|
66
66
|
"agent",
|