@llm4ts/flow 2.8.0 → 2.9.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/dist/Approval.d.ts +1 -1
- package/dist/Approval.d.ts.map +1 -1
- package/dist/Artifacts.d.ts +2 -2
- package/dist/Artifacts.d.ts.map +1 -1
- package/dist/BenchReport.d.ts +2 -2
- package/dist/BenchReport.d.ts.map +1 -1
- package/dist/CostLedger.d.ts +2 -2
- package/dist/CostLedger.d.ts.map +1 -1
- package/dist/Equiv.d.ts +3 -3
- package/dist/Equiv.d.ts.map +1 -1
- package/dist/Flow.d.ts +1 -1
- package/dist/Flow.d.ts.map +1 -1
- package/dist/FlowContext.d.ts +15 -1
- package/dist/FlowContext.d.ts.map +1 -1
- package/dist/FlowContext.js.map +1 -1
- package/dist/FlowError.d.ts +24 -1
- package/dist/FlowError.d.ts.map +1 -1
- package/dist/FlowError.js +29 -0
- package/dist/FlowError.js.map +1 -1
- package/dist/Judgment.d.ts +2 -2
- package/dist/Judgment.d.ts.map +1 -1
- package/dist/Persistence.d.ts +2 -2
- package/dist/Persistence.d.ts.map +1 -1
- package/dist/PlanExecution.d.ts +1 -1
- package/dist/PlanExecution.d.ts.map +1 -1
- package/dist/ProgramJudge.d.ts +1 -1
- package/dist/ProgramJudge.d.ts.map +1 -1
- package/dist/Replay.d.ts +2 -2
- package/dist/Replay.d.ts.map +1 -1
- package/dist/Review.d.ts +3 -3
- package/dist/Review.d.ts.map +1 -1
- package/dist/ReviewCache.d.ts +1 -1
- package/dist/ReviewCache.d.ts.map +1 -1
- package/dist/Roster.d.ts +163 -0
- package/dist/Roster.d.ts.map +1 -0
- package/dist/Roster.js +560 -0
- package/dist/Roster.js.map +1 -0
- package/dist/RosterSeats.d.ts +71 -0
- package/dist/RosterSeats.d.ts.map +1 -0
- package/dist/RosterSeats.js +144 -0
- package/dist/RosterSeats.js.map +1 -0
- package/dist/Stories.d.ts +9 -5
- package/dist/Stories.d.ts.map +1 -1
- package/dist/Stories.js +70 -10
- package/dist/Stories.js.map +1 -1
- package/package.json +4 -2
- package/src/FlowContext.ts +19 -1
- package/src/FlowError.ts +32 -0
- package/src/Roster.ts +824 -0
- package/src/RosterSeats.ts +306 -0
- package/src/Stories.ts +97 -14
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Scope from "effect/Scope";
|
|
3
|
+
import type { LlmServiceShape } from "@llm4ts/core/LlmService";
|
|
4
|
+
import { type ConnectorCapabilities } from "@llm4ts/core/Models";
|
|
5
|
+
import { type FlowError } from "./FlowError.ts";
|
|
6
|
+
import { type FlowEventsShape } from "./FlowEvents.ts";
|
|
7
|
+
import { type ExecutorSpec, type Role, type RosterShape } from "./Roster.ts";
|
|
8
|
+
/** An executor's connector, as the runner resolved it for a role and a directory. */
|
|
9
|
+
export type RosterSeat = LlmServiceShape & {
|
|
10
|
+
readonly capabilities: ConnectorCapabilities;
|
|
11
|
+
};
|
|
12
|
+
export interface SeatSource {
|
|
13
|
+
readonly seatFor: (executor: ExecutorSpec, role: Role, workDir: string) => Effect.Effect<RosterSeat, FlowError>;
|
|
14
|
+
}
|
|
15
|
+
/** What a flow context knows about the roster behind its seats. */
|
|
16
|
+
export interface RosterView {
|
|
17
|
+
/** A seat for `role`, leased per call (independent of this context's coder where possible). */
|
|
18
|
+
readonly forRole: (role: Role) => LlmServiceShape;
|
|
19
|
+
/** Free slots for `role` right now. */
|
|
20
|
+
readonly available: (role: Role) => Effect.Effect<number>;
|
|
21
|
+
/** Configured slots for `role`. */
|
|
22
|
+
readonly slots: (role: Role) => number;
|
|
23
|
+
/** The executor holding this context's coder, once leased. */
|
|
24
|
+
readonly executor: Effect.Effect<string | undefined>;
|
|
25
|
+
/** Every executor that held this context's coder, in order (handovers). */
|
|
26
|
+
readonly history: Effect.Effect<ReadonlyArray<string>>;
|
|
27
|
+
}
|
|
28
|
+
export interface RosterSeatOptions {
|
|
29
|
+
readonly events: FlowEventsShape;
|
|
30
|
+
/** Executors this seat must not use: the context's coder, for independence. */
|
|
31
|
+
readonly avoid?: Effect.Effect<ReadonlyArray<string>>;
|
|
32
|
+
/** Used without a slot when nobody outside `avoid` can ever serve the role. */
|
|
33
|
+
readonly borrow?: Effect.Effect<ExecutorSpec | undefined>;
|
|
34
|
+
/** Who is asking, for the events. */
|
|
35
|
+
readonly label?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A seat for `role` that leases an executor for each call and releases it
|
|
39
|
+
* when the call (or its stream) ends. A failure is reported to the roster,
|
|
40
|
+
* which excludes the executor when the failure says it is out of quota or
|
|
41
|
+
* down; the call itself still fails — a per-call seat has nothing to hand
|
|
42
|
+
* over, and the retry layer below has already had its turn.
|
|
43
|
+
*/
|
|
44
|
+
export declare const rosterSeat: (roster: RosterShape, source: SeatSource, role: Role, workDir: string, options: RosterSeatOptions) => LlmServiceShape;
|
|
45
|
+
export interface HeldCoderOptions {
|
|
46
|
+
readonly events: FlowEventsShape;
|
|
47
|
+
/** Lease now (a story waiting for capacity), or on the first call (a flow's root coder). */
|
|
48
|
+
readonly eager: boolean;
|
|
49
|
+
/** The executor this context held before, taken first while free. */
|
|
50
|
+
readonly prefer?: string;
|
|
51
|
+
/** Handovers before a failure surfaces. Default 2. */
|
|
52
|
+
readonly maxHandovers?: number;
|
|
53
|
+
readonly label?: string;
|
|
54
|
+
}
|
|
55
|
+
export interface HeldCoder {
|
|
56
|
+
readonly service: LlmServiceShape;
|
|
57
|
+
readonly executor: Effect.Effect<string | undefined>;
|
|
58
|
+
readonly lease: Effect.Effect<ExecutorSpec | undefined>;
|
|
59
|
+
readonly history: Effect.Effect<ReadonlyArray<string>>;
|
|
60
|
+
}
|
|
61
|
+
export declare const takeoverNote: (from: string, reason: string) => string;
|
|
62
|
+
/**
|
|
63
|
+
* The coder of one context (a story, or a flow's root): one executor held
|
|
64
|
+
* for the context's lifetime. When a call fails in a way that takes that
|
|
65
|
+
* executor out of the round (a usage limit, a dead serving engine), the
|
|
66
|
+
* lease is released, the next coder leased, and the call repeated there
|
|
67
|
+
* with a takeover note — the chat history the flow replays, the task
|
|
68
|
+
* checkpoint and the working tree carry the work across.
|
|
69
|
+
*/
|
|
70
|
+
export declare const makeHeldCoder: (roster: RosterShape, source: SeatSource, workDir: string, options: HeldCoderOptions) => Effect.Effect<HeldCoder, import("./FlowError.ts").PersistenceError | import("./FlowError.ts").PlanParseError | import("./FlowError.ts").UnsupportedSchemaVersion | import("./FlowError.ts").WorkspacePathError | import("./FlowError.ts").WorkspaceLimitError | import("./FlowError.ts").WorkspaceIoError | import("./FlowError.ts").FlowAborted | import("./FlowError.ts").ProcessError | import("./FlowError.ts").FlowLlmError | import("./FlowError.ts").FlowCapabilityDenied | import("./FlowError.ts").ColumnNotFound | import("./FlowError.ts").BudgetExceeded | import("./FlowError.ts").StoryPlanInvalid | import("./FlowError.ts").PerimeterViolation | import("./FlowError.ts").MissingDependency | import("./FlowError.ts").MergeConflict | import("./FlowError.ts").EpicCheckoutDirty | import("./FlowError.ts").RosterInvalid | import("./FlowError.ts").RosterExhausted | import("./FlowError.ts").StoryFailed | import("./FlowError.ts").DecisionsInvalid | import("./FlowError.ts").OpenPointsPending | import("./FlowError.ts").ContractConflict, Scope.Scope>;
|
|
71
|
+
//# sourceMappingURL=RosterSeats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RosterSeats.d.ts","sourceRoot":"","sources":["../src/RosterSeats.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAIrC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAC9D,OAAO,EAAW,KAAK,qBAAqB,EAAiB,MAAM,qBAAqB,CAAA;AACxF,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAClE,OAAO,EAAQ,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAC5D,OAAO,EAGL,KAAK,YAAY,EAEjB,KAAK,IAAI,EACT,KAAK,WAAW,EACjB,MAAM,aAAa,CAAA;AAEpB,qFAAqF;AACrF,MAAM,MAAM,UAAU,GAAG,eAAe,GAAG;IAAE,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAA;CAAE,CAAA;AAE3F,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,CAChB,QAAQ,EAAE,YAAY,EACtB,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,MAAM,KACZ,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC,CAAA;CAC1C;AAED,mEAAmE;AACnE,MAAM,WAAW,UAAU;IACzB,+FAA+F;IAC/F,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,eAAe,CAAA;IACjD,uCAAuC;IACvC,QAAQ,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IACzD,mCAAmC;IACnC,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,CAAA;IACtC,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IACpD,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAA;CACvD;AAwDD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAA;IAChC,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAA;IACrD,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,YAAY,GAAG,SAAS,CAAC,CAAA;IACzD,qCAAqC;IACrC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,GACrB,QAAQ,WAAW,EACnB,QAAQ,UAAU,EAClB,MAAM,IAAI,EACV,SAAS,MAAM,EACf,SAAS,iBAAiB,KACzB,eA8CF,CAAA;AAID,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAA;IAChC,4FAA4F;IAC5F,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,qEAAqE;IACrE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,sDAAsD;IACtD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAA;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IACpD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,YAAY,GAAG,SAAS,CAAC,CAAA;IACvD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAA;CACvD;AAED,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,EAAE,QAAQ,MAAM,KAAG,MAK/C,CAAA;AAOb;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,0mCA2FxB,CAAA"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// Seats over the executor roster (ADR 0019). A flow keeps calling an
|
|
2
|
+
// `LlmServiceShape`; behind it, a reasoning seat leases an executor for each
|
|
3
|
+
// call, and a coder seat holds one executor for its context and hands over
|
|
4
|
+
// to the next when that one is taken out of the round. The runner supplies
|
|
5
|
+
// `seatFor`, which turns an executor into a connector rooted in a directory.
|
|
6
|
+
import * as Effect from "effect/Effect";
|
|
7
|
+
import * as Ref from "effect/Ref";
|
|
8
|
+
import * as Scope from "effect/Scope";
|
|
9
|
+
import * as Semaphore from "effect/Semaphore";
|
|
10
|
+
import * as Stream from "effect/Stream";
|
|
11
|
+
import { ConfigError } from "@llm4ts/core/Errors";
|
|
12
|
+
import { Message } from "@llm4ts/core/Models";
|
|
13
|
+
import { describeFlowError } from "./FlowError.js";
|
|
14
|
+
import { Info } from "./FlowEvents.js";
|
|
15
|
+
import { describeExclusion, hasRole } from "./Roster.js";
|
|
16
|
+
/** A roster failure seen through an `LlmServiceShape`, whose failures are `LlmError`s. */
|
|
17
|
+
const asLlmError = (error) => ConfigError.make({ message: describeFlowError(error) });
|
|
18
|
+
const say = (events, message) => events.publish(Info.make({ message: `roster: ${message}` }));
|
|
19
|
+
const withNote = (note, prompt) => note === undefined ? prompt : `${note}\n\n${prompt}`;
|
|
20
|
+
const withNoteOnLast = (note, messages) => {
|
|
21
|
+
const last = messages.at(-1);
|
|
22
|
+
if (note === undefined || last === undefined) {
|
|
23
|
+
return messages;
|
|
24
|
+
}
|
|
25
|
+
return [
|
|
26
|
+
...messages.slice(0, -1),
|
|
27
|
+
Message.make({ ...last, content: withNote(note, last.content) })
|
|
28
|
+
];
|
|
29
|
+
};
|
|
30
|
+
const serviceOver = (runner) => ({
|
|
31
|
+
executeStream: (prompt) => runner.stream((seat, note) => seat.executeStream(withNote(note, prompt))),
|
|
32
|
+
executeStreamWithHistory: (messages) => runner.stream((seat, note) => seat.executeStreamWithHistory(withNoteOnLast(note, messages))),
|
|
33
|
+
executeWithTools: (prompt, tools) => runner.run((seat, note) => seat.executeWithTools(withNote(note, prompt), tools)),
|
|
34
|
+
executeStructured: (prompt, schema, jsonSchema) => runner.run((seat, note) => seat.executeStructured(withNote(note, prompt), schema, jsonSchema)),
|
|
35
|
+
executeStructuredWithUsage: (prompt, schema, jsonSchema) => runner.run((seat, note) => seat.executeStructuredWithUsage(withNote(note, prompt), schema, jsonSchema)),
|
|
36
|
+
scoreLabels: (prompt, labels) => runner.run((seat) => seat.scoreLabels(prompt, labels)),
|
|
37
|
+
isAvailable: runner.isAvailable
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* A seat for `role` that leases an executor for each call and releases it
|
|
41
|
+
* when the call (or its stream) ends. A failure is reported to the roster,
|
|
42
|
+
* which excludes the executor when the failure says it is out of quota or
|
|
43
|
+
* down; the call itself still fails — a per-call seat has nothing to hand
|
|
44
|
+
* over, and the retry layer below has already had its turn.
|
|
45
|
+
*/
|
|
46
|
+
export const rosterSeat = (roster, source, role, workDir, options) => {
|
|
47
|
+
const acquire = Effect.gen(function* () {
|
|
48
|
+
const avoid = options.avoid === undefined ? [] : yield* options.avoid;
|
|
49
|
+
if (avoid.length > 0 && !(yield* roster.canEverServe(role, avoid))) {
|
|
50
|
+
const borrowed = options.borrow === undefined ? undefined : yield* options.borrow;
|
|
51
|
+
if (borrowed !== undefined && hasRole(borrowed, role)) {
|
|
52
|
+
yield* say(options.events, `${borrowed.id} takes ${role}${options.label === undefined ? "" : ` for ${options.label}`} on its own coder's slot — not independent (no other executor can take ${role})`);
|
|
53
|
+
return borrowed;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const lease = yield* roster
|
|
57
|
+
.lease(role, { avoid, ...(options.label === undefined ? {} : { label: options.label }) })
|
|
58
|
+
.pipe(Effect.mapError(asLlmError));
|
|
59
|
+
return lease.executor;
|
|
60
|
+
});
|
|
61
|
+
const seatOf = (executor) => source.seatFor(executor, role, workDir).pipe(Effect.mapError(asLlmError));
|
|
62
|
+
const reportOn = (executor) => (error) => Effect.asVoid(roster.report(executor.id, error));
|
|
63
|
+
return serviceOver({
|
|
64
|
+
run: (use) => Effect.scoped(Effect.gen(function* () {
|
|
65
|
+
const executor = yield* acquire;
|
|
66
|
+
const seat = yield* seatOf(executor);
|
|
67
|
+
return yield* use(seat, undefined).pipe(Effect.tapError(reportOn(executor)));
|
|
68
|
+
})),
|
|
69
|
+
stream: (use) => Stream.unwrap(Effect.gen(function* () {
|
|
70
|
+
const executor = yield* acquire;
|
|
71
|
+
const seat = yield* seatOf(executor);
|
|
72
|
+
return use(seat, undefined).pipe(Stream.tapError(reportOn(executor)));
|
|
73
|
+
})),
|
|
74
|
+
isAvailable: roster.canEverServe(role)
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
export const takeoverNote = (from, reason) => [
|
|
78
|
+
`(You are taking over this work from another coding agent ('${from}'), which had to stop:`,
|
|
79
|
+
`${reason}. Its changes so far are in the working tree: inspect them (git status, git diff)`,
|
|
80
|
+
"before continuing, and carry on from there.)"
|
|
81
|
+
].join(" ");
|
|
82
|
+
/**
|
|
83
|
+
* The coder of one context (a story, or a flow's root): one executor held
|
|
84
|
+
* for the context's lifetime. When a call fails in a way that takes that
|
|
85
|
+
* executor out of the round (a usage limit, a dead serving engine), the
|
|
86
|
+
* lease is released, the next coder leased, and the call repeated there
|
|
87
|
+
* with a takeover note — the chat history the flow replays, the task
|
|
88
|
+
* checkpoint and the working tree carry the work across.
|
|
89
|
+
*/
|
|
90
|
+
export const makeHeldCoder = Effect.fn("@llm4ts/flow/RosterSeats.heldCoder")(function* (roster, source, workDir, options) {
|
|
91
|
+
const scope = yield* Effect.scope;
|
|
92
|
+
const current = yield* Ref.make(undefined);
|
|
93
|
+
const history = yield* Ref.make([]);
|
|
94
|
+
const handovers = yield* Ref.make(0);
|
|
95
|
+
const prefer = yield* Ref.make(options.prefer);
|
|
96
|
+
const gate = yield* Semaphore.make(1);
|
|
97
|
+
const maxHandovers = options.maxHandovers ?? 2;
|
|
98
|
+
const acquire = gate.withPermit(Effect.gen(function* () {
|
|
99
|
+
const held = yield* Ref.get(current);
|
|
100
|
+
if (held !== undefined) {
|
|
101
|
+
return held;
|
|
102
|
+
}
|
|
103
|
+
const preferred = yield* Ref.get(prefer);
|
|
104
|
+
const lease = yield* roster
|
|
105
|
+
.lease("coder", {
|
|
106
|
+
...(preferred === undefined ? {} : { prefer: preferred }),
|
|
107
|
+
...(options.label === undefined ? {} : { label: options.label })
|
|
108
|
+
})
|
|
109
|
+
.pipe(Scope.provide(scope));
|
|
110
|
+
const seat = yield* source.seatFor(lease.executor, "coder", workDir);
|
|
111
|
+
const next = { lease, seat };
|
|
112
|
+
yield* Ref.set(current, next);
|
|
113
|
+
yield* Ref.update(history, (ids) => ids.at(-1) === lease.executor.id ? ids : [...ids, lease.executor.id]);
|
|
114
|
+
return next;
|
|
115
|
+
}));
|
|
116
|
+
if (options.eager) {
|
|
117
|
+
yield* acquire;
|
|
118
|
+
}
|
|
119
|
+
/** Decides whether a failure hands over; returns the takeover note, or fails with the error. */
|
|
120
|
+
const handover = (held, error) => Effect.gen(function* () {
|
|
121
|
+
const exclusion = yield* roster.report(held.lease.executor.id, error);
|
|
122
|
+
if (exclusion === undefined || (yield* Ref.get(handovers)) >= maxHandovers) {
|
|
123
|
+
return yield* Effect.fail(error);
|
|
124
|
+
}
|
|
125
|
+
yield* Ref.update(handovers, (count) => count + 1);
|
|
126
|
+
yield* Ref.set(prefer, undefined);
|
|
127
|
+
yield* held.lease.release;
|
|
128
|
+
yield* Ref.set(current, undefined);
|
|
129
|
+
yield* say(options.events, `${options.label ?? workDir}: handing the coder over from ${held.lease.executor.id} (${describeExclusion(exclusion)})`);
|
|
130
|
+
return takeoverNote(held.lease.executor.id, exclusion.reason);
|
|
131
|
+
});
|
|
132
|
+
const run = (use, note) => Effect.gen(function* () {
|
|
133
|
+
const held = yield* acquire.pipe(Effect.mapError(asLlmError));
|
|
134
|
+
return yield* use(held.seat, note).pipe(Effect.catch((error) => Effect.flatMap(handover(held, error), (next) => run(use, next))));
|
|
135
|
+
});
|
|
136
|
+
const stream = (use, note) => Stream.unwrap(Effect.map(acquire.pipe(Effect.mapError(asLlmError)), (held) => use(held.seat, note).pipe(Stream.catchIf(() => true, (error) => Stream.unwrap(Effect.map(handover(held, error), (next) => stream(use, next)))))));
|
|
137
|
+
return {
|
|
138
|
+
service: serviceOver({ run, stream, isAvailable: roster.canEverServe("coder") }),
|
|
139
|
+
executor: Effect.map(Ref.get(current), (held) => held?.lease.executor.id),
|
|
140
|
+
lease: Effect.map(Ref.get(current), (held) => held?.lease.executor),
|
|
141
|
+
history: Ref.get(history)
|
|
142
|
+
};
|
|
143
|
+
});
|
|
144
|
+
//# sourceMappingURL=RosterSeats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RosterSeats.js","sourceRoot":"","sources":["../src/RosterSeats.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,6EAA6E;AAC7E,2EAA2E;AAC3E,2EAA2E;AAC3E,6EAA6E;AAC7E,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,GAAG,MAAM,YAAY,CAAA;AACjC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,SAAS,MAAM,kBAAkB,CAAA;AAC7C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,WAAW,EAAiB,MAAM,qBAAqB,CAAA;AAEhE,OAAO,EAAE,OAAO,EAA6C,MAAM,qBAAqB,CAAA;AACxF,OAAO,EAAE,iBAAiB,EAAkB,MAAM,gBAAgB,CAAA;AAClE,OAAO,EAAE,IAAI,EAAwB,MAAM,iBAAiB,CAAA;AAC5D,OAAO,EACL,iBAAiB,EACjB,OAAO,EAKR,MAAM,aAAa,CAAA;AA2BpB,0FAA0F;AAC1F,MAAM,UAAU,GAAG,CAAC,KAAgB,EAAY,EAAE,CAChD,WAAW,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;AAEzD,MAAM,GAAG,GAAG,CAAC,MAAuB,EAAE,OAAe,EAAuB,EAAE,CAC5E,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,WAAW,OAAO,EAAE,EAAE,CAAC,CAAC,CAAA;AAa9D,MAAM,QAAQ,GAAG,CAAC,IAAwB,EAAE,MAAc,EAAU,EAAE,CACpE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,OAAO,MAAM,EAAE,CAAA;AAEtD,MAAM,cAAc,GAAG,CACrB,IAAwB,EACxB,QAAgC,EACR,EAAE;IAC1B,MAAM,IAAI,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;IAC5B,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QAC7C,OAAO,QAAQ,CAAA;IACjB,CAAC;IACD,OAAO;QACL,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACxB,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;KACjE,CAAA;AACH,CAAC,CAAA;AAED,MAAM,WAAW,GAAG,CAAC,MAAkB,EAAmB,EAAE,CAAC,CAAC;IAC5D,aAAa,EAAE,CAAC,MAAM,EAAE,EAAE,CACxB,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3E,wBAAwB,EAAE,CAAC,QAAQ,EAAE,EAAE,CACrC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,wBAAwB,CAAC,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC9F,gBAAgB,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAClC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC;IAClF,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,CAChD,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;IAChG,0BAA0B,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,CACzD,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CACxB,IAAI,CAAC,0BAA0B,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,CAC5E;IACH,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvF,WAAW,EAAE,MAAM,CAAC,WAAW;CAChC,CAAC,CAAA;AAcF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CACxB,MAAmB,EACnB,MAAkB,EAClB,IAAU,EACV,OAAe,EACf,OAA0B,EACT,EAAE;IACnB,MAAM,OAAO,GAAuD,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACtF,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,CAAA;QACrE,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC;YACnE,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAA;YACjF,IAAI,QAAQ,KAAK,SAAS,IAAI,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC;gBACtD,KAAK,CAAC,CAAC,GAAG,CACR,OAAO,CAAC,MAAM,EACd,GAAG,QAAQ,CAAC,EAAE,UAAU,IAAI,GAAG,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,OAAO,CAAC,KAAK,EAAE,0EAA0E,IAAI,GAAG,CAC3K,CAAA;gBACD,OAAO,QAAQ,CAAA;YACjB,CAAC;QACH,CAAC;QACD,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,MAAM;aACxB,KAAK,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC;aACxF,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAA;QACpC,OAAO,KAAK,CAAC,QAAQ,CAAA;IACvB,CAAC,CAAC,CAAA;IAEF,MAAM,MAAM,GAAG,CAAC,QAAsB,EAAuC,EAAE,CAC7E,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAA;IAE3E,MAAM,QAAQ,GACZ,CAAC,QAAsB,EAAE,EAAE,CAC3B,CAAC,KAAe,EAAuB,EAAE,CACvC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CAAA;IAEpD,OAAO,WAAW,CAAC;QACjB,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE,CACX,MAAM,CAAC,MAAM,CACX,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YAClB,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,OAAO,CAAA;YAC/B,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;YACpC,OAAO,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;QAC9E,CAAC,CAAC,CACH;QACH,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CACd,MAAM,CAAC,MAAM,CACX,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YAClB,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,OAAO,CAAA;YAC/B,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;YACpC,OAAO,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;QACvE,CAAC,CAAC,CACH;QACH,WAAW,EAAE,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC;KACvC,CAAC,CAAA;AACJ,CAAC,CAAA;AAsBD,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,IAAY,EAAE,MAAc,EAAU,EAAE,CACnE;IACE,8DAA8D,IAAI,wBAAwB;IAC1F,GAAG,MAAM,mFAAmF;IAC5F,8CAA8C;CAC/C,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAOb;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,EAAE,CAAC,oCAAoC,CAAC,CAAC,QAAQ,CAAC,EACpF,MAAmB,EACnB,MAAkB,EAClB,OAAe,EACf,OAAyB;IAEzB,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAA;IACjC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,CAAmB,SAAS,CAAC,CAAA;IAC5D,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,CAAwB,EAAE,CAAC,CAAA;IAC1D,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACpC,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;IAC9C,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACrC,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,CAAC,CAAA;IAE9C,MAAM,OAAO,GAAmC,IAAI,CAAC,UAAU,CAC7D,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QACpC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,OAAO,IAAI,CAAA;QACb,CAAC;QACD,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QACxC,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,MAAM;aACxB,KAAK,CAAC,OAAO,EAAE;YACd,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;YACzD,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;SACjE,CAAC;aACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAA;QAC7B,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;QACpE,MAAM,IAAI,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;QAC5B,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;QAC7B,KAAK,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CACjC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,EAAE,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CACrE,CAAA;QACD,OAAO,IAAI,CAAA;IACb,CAAC,CAAC,CACH,CAAA;IAED,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,KAAK,CAAC,CAAC,OAAO,CAAA;IAChB,CAAC;IAED,gGAAgG;IAChG,MAAM,QAAQ,GAAG,CAAC,IAAU,EAAE,KAAe,EAAmC,EAAE,CAChF,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC,CAAA;QACrE,IAAI,SAAS,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,YAAY,EAAE,CAAC;YAC3E,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAClC,CAAC;QACD,KAAK,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC,CAAA;QAClD,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;QACjC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAA;QACzB,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;QAClC,KAAK,CAAC,CAAC,GAAG,CACR,OAAO,CAAC,MAAM,EACd,GAAG,OAAO,CAAC,KAAK,IAAI,OAAO,iCAAiC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,KAAK,iBAAiB,CAAC,SAAS,CAAC,GAAG,CACvH,CAAA;QACD,OAAO,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC,MAAM,CAAC,CAAA;IAC/D,CAAC,CAAC,CAAA;IAEJ,MAAM,GAAG,GAAG,CACV,GAAkF,EAClF,IAAa,EACkB,EAAE,CACjC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAA;QAC7D,OAAO,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,IAAI,CACrC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CACzF,CAAA;IACH,CAAC,CAAC,CAAA;IAEJ,MAAM,MAAM,GAAG,CACb,GAAsF,EACtF,IAAa,EACsB,EAAE,CACrC,MAAM,CAAC,MAAM,CACX,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAC7D,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,IAAI,CACvB,MAAM,CAAC,OAAO,CACZ,GAAG,EAAE,CAAC,IAAI,EACV,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CACzF,CACF,CACF,CACF,CAAA;IAEH,OAAO;QACL,OAAO,EAAE,WAAW,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,CAAC;QAChF,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC;QACnE,OAAO,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC;KAC1B,CAAA;AACH,CAAC,CAAC,CAAA"}
|
package/dist/Stories.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type * as Scope from "effect/Scope";
|
|
|
5
5
|
import type { LlmServiceShape } from "@llm4ts/core/LlmService";
|
|
6
6
|
import type { TokenUsage } from "@llm4ts/core/Models";
|
|
7
7
|
import { type BoardSyncShape } from "./BoardSync.ts";
|
|
8
|
-
import type { FlowContextShape } from "./FlowContext.ts";
|
|
8
|
+
import type { ContextOptions, FlowContextShape } from "./FlowContext.ts";
|
|
9
9
|
import { EpicCheckoutDirty, MissingDependency, PerimeterViolation, StoryFailed, type FlowError } from "./FlowError.ts";
|
|
10
10
|
import { type PlainFileStoreShape } from "./Persistence.ts";
|
|
11
11
|
import { Plan } from "./Plan.ts";
|
|
@@ -28,6 +28,8 @@ declare const StoryState_base: Schema.Class<StoryState, Schema.Struct<{
|
|
|
28
28
|
readonly branch: Schema.String;
|
|
29
29
|
readonly worktree: Schema.String;
|
|
30
30
|
readonly status: Schema.Literals<readonly ["started", "merged", "failed"]>;
|
|
31
|
+
/** The roster executor that last held the story's coder (ADR 0019); preferred on resume. */
|
|
32
|
+
readonly executor: Schema.optionalKey<Schema.String>;
|
|
31
33
|
}>, {}>;
|
|
32
34
|
/** What the executor remembers about a story between runs. */
|
|
33
35
|
export declare class StoryState extends StoryState_base {
|
|
@@ -44,6 +46,8 @@ declare const StoryOutcome_base: Schema.Class<StoryOutcome, Schema.Struct<{
|
|
|
44
46
|
/** Failure reason, or what a waiting story waits for. */
|
|
45
47
|
readonly reason: Schema.optionalKey<Schema.String>;
|
|
46
48
|
readonly judge: Schema.optionalKey<Schema.String>;
|
|
49
|
+
/** The roster executor(s) that coded the story, in order ("codex → claude"). */
|
|
50
|
+
readonly executor: Schema.optionalKey<Schema.String>;
|
|
47
51
|
/** ESTIMATES, never measurements (ADR 0012). */
|
|
48
52
|
readonly estimatedTokens: Schema.optionalKey<Schema.Int>;
|
|
49
53
|
readonly estimatedCostUsd: Schema.optionalKey<Schema.Number>;
|
|
@@ -122,7 +126,7 @@ export interface StoriesOptions {
|
|
|
122
126
|
/** Default `epic/<epicId>`. */
|
|
123
127
|
readonly epicBranch?: string;
|
|
124
128
|
/** Seats rooted in a worktree; the executor never resolves seats itself. */
|
|
125
|
-
readonly contextFor: (workDir: string) => Effect.Effect<StorySeats, FlowError, Scope.Scope>;
|
|
129
|
+
readonly contextFor: (workDir: string, options?: ContextOptions) => Effect.Effect<StorySeats, FlowError, Scope.Scope>;
|
|
126
130
|
readonly board: BoardSyncShape;
|
|
127
131
|
/**
|
|
128
132
|
* Prepares a worktree before its coder runs — a fresh checkout has no
|
|
@@ -133,7 +137,7 @@ export interface StoriesOptions {
|
|
|
133
137
|
/** The target's gates, run in a worktree per task and on the epic checkout after each merge. */
|
|
134
138
|
readonly gates: (workDir: string) => Effect.Effect<ReviewResult, FlowError>;
|
|
135
139
|
/** Story-level judge over the branch's diff against the epic branch; omit to skip. */
|
|
136
|
-
readonly judge?: (story: Story, diff: string) => Effect.Effect<ReviewResult, FlowError>;
|
|
140
|
+
readonly judge?: (story: Story, diff: string, seats: StorySeats) => Effect.Effect<ReviewResult, FlowError>;
|
|
137
141
|
/** Judge attempts, each but the last followed by one coder feedback round. Default 2. */
|
|
138
142
|
readonly judgeRounds?: number;
|
|
139
143
|
/** Extra system context per story (house rules, shared read-only excerpts). */
|
|
@@ -151,7 +155,7 @@ export interface StoriesOptions {
|
|
|
151
155
|
* story owns, or a parallel story's). `real: false` sends the coder back
|
|
152
156
|
* once with the reason. Omit to accept every such claim.
|
|
153
157
|
*/
|
|
154
|
-
readonly verifyBlocked?: (story: Story, need: string, workDir: string) => Effect.Effect<BlockedVerdict, FlowError>;
|
|
158
|
+
readonly verifyBlocked?: (story: Story, need: string, workDir: string, seats: StorySeats) => Effect.Effect<BlockedVerdict, FlowError>;
|
|
155
159
|
/**
|
|
156
160
|
* Waits until a serving engine that went down (`isOutageMessage`) is back;
|
|
157
161
|
* the story it interrupted is then retried once. Omit, or fail it, and the
|
|
@@ -159,6 +163,6 @@ export interface StoriesOptions {
|
|
|
159
163
|
*/
|
|
160
164
|
readonly awaitRecovery?: (reason: string) => Effect.Effect<void, FlowError>;
|
|
161
165
|
}
|
|
162
|
-
export declare const implementStoriesFlow: (context: FlowContextShape, options: StoriesOptions) => Effect.Effect<EpicReport, import("./FlowError.ts").PersistenceError | import("./FlowError.ts").PlanParseError | import("./FlowError.ts").UnsupportedSchemaVersion | import("./FlowError.ts").WorkspacePathError | import("./FlowError.ts").WorkspaceLimitError | import("./FlowError.ts").WorkspaceIoError | import("./FlowError.ts").FlowAborted | import("./FlowError.ts").ProcessError | import("./FlowError.ts").FlowLlmError | import("./FlowError.ts").FlowCapabilityDenied | import("./FlowError.ts").ColumnNotFound | import("./FlowError.ts").BudgetExceeded | import("./FlowError.ts").StoryPlanInvalid | PerimeterViolation | MissingDependency | import("./FlowError.ts").MergeConflict | EpicCheckoutDirty | StoryFailed | import("./FlowError.ts").DecisionsInvalid | import("./FlowError.ts").OpenPointsPending | import("./FlowError.ts").ContractConflict, never>;
|
|
166
|
+
export declare const implementStoriesFlow: (context: FlowContextShape, options: StoriesOptions) => Effect.Effect<EpicReport, import("./FlowError.ts").PersistenceError | import("./FlowError.ts").PlanParseError | import("./FlowError.ts").UnsupportedSchemaVersion | import("./FlowError.ts").WorkspacePathError | import("./FlowError.ts").WorkspaceLimitError | import("./FlowError.ts").WorkspaceIoError | import("./FlowError.ts").FlowAborted | import("./FlowError.ts").ProcessError | import("./FlowError.ts").FlowLlmError | import("./FlowError.ts").FlowCapabilityDenied | import("./FlowError.ts").ColumnNotFound | import("./FlowError.ts").BudgetExceeded | import("./FlowError.ts").StoryPlanInvalid | PerimeterViolation | MissingDependency | import("./FlowError.ts").MergeConflict | EpicCheckoutDirty | import("./FlowError.ts").RosterInvalid | import("./FlowError.ts").RosterExhausted | StoryFailed | import("./FlowError.ts").DecisionsInvalid | import("./FlowError.ts").OpenPointsPending | import("./FlowError.ts").ContractConflict, never>;
|
|
163
167
|
export {};
|
|
164
168
|
//# sourceMappingURL=Stories.d.ts.map
|
package/dist/Stories.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Stories.d.ts","sourceRoot":"","sources":["../src/Stories.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAGvC,OAAO,KAAK,GAAG,MAAM,YAAY,CAAA;AACjC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,KAAK,KAAK,MAAM,cAAc,CAAA;AAG1C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAC9D,OAAO,KAAK,EAAY,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAE/D,OAAO,EAAa,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAG/D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"Stories.d.ts","sourceRoot":"","sources":["../src/Stories.ts"],"names":[],"mappings":"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"}
|
package/dist/Stories.js
CHANGED
|
@@ -14,7 +14,7 @@ import * as Stream from "effect/Stream";
|
|
|
14
14
|
import { BoardItem } from "./BoardSync.js";
|
|
15
15
|
import { makeChat } from "./Chat.js";
|
|
16
16
|
import { implementPlanFlow } from "./Flow.js";
|
|
17
|
-
import { describeFlowError, EpicCheckoutDirty, MissingDependency, PerimeterViolation, StoryFailed } from "./FlowError.js";
|
|
17
|
+
import { describeFlowError, rosterExhaustedPrefix, EpicCheckoutDirty, MissingDependency, PerimeterViolation, StoryFailed } from "./FlowError.js";
|
|
18
18
|
import { Info } from "./FlowEvents.js";
|
|
19
19
|
import { statusPaths } from "./GitTool.js";
|
|
20
20
|
import { checkPerimeter, enforcePerimeter, isWithinPerimeter, perimeterGate, strayTasks } from "./Perimeter.js";
|
|
@@ -36,7 +36,9 @@ export class StoryState extends Schema.Class("StoryState")({
|
|
|
36
36
|
hash: Schema.String,
|
|
37
37
|
branch: Schema.String,
|
|
38
38
|
worktree: Schema.String,
|
|
39
|
-
status: StoryStatus
|
|
39
|
+
status: StoryStatus,
|
|
40
|
+
/** The roster executor that last held the story's coder (ADR 0019); preferred on resume. */
|
|
41
|
+
executor: Schema.optionalKey(Schema.String)
|
|
40
42
|
}) {
|
|
41
43
|
}
|
|
42
44
|
export const EpicReportVersion = 1;
|
|
@@ -50,6 +52,8 @@ export class StoryOutcome extends Schema.Class("StoryOutcome")({
|
|
|
50
52
|
/** Failure reason, or what a waiting story waits for. */
|
|
51
53
|
reason: Schema.optionalKey(Schema.String),
|
|
52
54
|
judge: Schema.optionalKey(Schema.String),
|
|
55
|
+
/** The roster executor(s) that coded the story, in order ("codex → claude"). */
|
|
56
|
+
executor: Schema.optionalKey(Schema.String),
|
|
53
57
|
/** ESTIMATES, never measurements (ADR 0012). */
|
|
54
58
|
estimatedTokens: Schema.optionalKey(Schema.Int),
|
|
55
59
|
estimatedCostUsd: Schema.optionalKey(Schema.Number)
|
|
@@ -93,6 +97,25 @@ export const renderEpicReport = (report) => {
|
|
|
93
97
|
if (cost.length > 0) {
|
|
94
98
|
lines.push(`- Estimated cost: ${money(cost.reduce((sum, value) => sum + value, 0))}`);
|
|
95
99
|
}
|
|
100
|
+
const coded = report.stories.filter((story) => story.executor !== undefined);
|
|
101
|
+
if (coded.length > 0) {
|
|
102
|
+
// A story is counted for the executor that finished it; a handover chain
|
|
103
|
+
// is shown in full next to the story.
|
|
104
|
+
const byExecutor = new Map();
|
|
105
|
+
for (const story of coded) {
|
|
106
|
+
const chain = story.executor ?? "";
|
|
107
|
+
const last = chain.split(" → ").at(-1) ?? chain;
|
|
108
|
+
const entry = byExecutor.get(last) ?? { stories: [], tokens: 0, cost: 0 };
|
|
109
|
+
entry.stories.push(chain === last ? story.id : `${story.id} (${chain})`);
|
|
110
|
+
entry.tokens += story.estimatedTokens ?? 0;
|
|
111
|
+
entry.cost += story.estimatedCostUsd ?? 0;
|
|
112
|
+
byExecutor.set(last, entry);
|
|
113
|
+
}
|
|
114
|
+
lines.push("", "## By executor", "");
|
|
115
|
+
for (const [executor, entry] of byExecutor) {
|
|
116
|
+
lines.push(`- ${executor}: ${entry.stories.join(", ")} — ~${entry.tokens} tokens, ${money(entry.cost)} (estimated)`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
96
119
|
lines.push("");
|
|
97
120
|
return lines.join("\n");
|
|
98
121
|
};
|
|
@@ -416,7 +439,15 @@ export const implementStoriesFlow = Effect.fn("@llm4ts/flow/Stories.implement")(
|
|
|
416
439
|
});
|
|
417
440
|
/** Everything that happens inside a story's worktree: plan, implement, judge, perimeter. */
|
|
418
441
|
const implementStory = Effect.fn("@llm4ts/flow/Stories.implementStory")(function* (story, state) {
|
|
419
|
-
const seats = yield* options.contextFor(state.worktree
|
|
442
|
+
const seats = yield* options.contextFor(state.worktree, {
|
|
443
|
+
label: `story ${story.id}`,
|
|
444
|
+
...(state.executor === undefined ? {} : { prefer: state.executor })
|
|
445
|
+
});
|
|
446
|
+
const roster = seats.context.roster;
|
|
447
|
+
const coderExecutor = roster === undefined ? undefined : yield* roster.executor;
|
|
448
|
+
if (coderExecutor !== undefined && coderExecutor !== state.executor) {
|
|
449
|
+
yield* saveVersioned(files, statePath(story), StoryStateVersion, StoryState, StoryState.make({ ...state, executor: coderExecutor }));
|
|
450
|
+
}
|
|
420
451
|
const blocked = yield* Ref.make(undefined);
|
|
421
452
|
const pushbacks = yield* Ref.make(0);
|
|
422
453
|
const storyContext = {
|
|
@@ -474,7 +505,7 @@ export const implementStoriesFlow = Effect.fn("@llm4ts/flow/Stories.implement")(
|
|
|
474
505
|
let rebuttal = blockedRebuttal(plan, story, need);
|
|
475
506
|
if (rebuttal === undefined && options.verifyBlocked !== undefined) {
|
|
476
507
|
const verdict = yield* options
|
|
477
|
-
.verifyBlocked(story, need, state.worktree)
|
|
508
|
+
.verifyBlocked(story, need, state.worktree, watchedSeats)
|
|
478
509
|
.pipe(Effect.catch(() => Effect.succeed(undefined)));
|
|
479
510
|
rebuttal = verdict === undefined || verdict.real ? undefined : verdict.reason;
|
|
480
511
|
}
|
|
@@ -564,7 +595,7 @@ export const implementStoriesFlow = Effect.fn("@llm4ts/flow/Stories.implement")(
|
|
|
564
595
|
// a model asked to score an empty diff scores the prompt instead.
|
|
565
596
|
return yield* failed(story, "the story branch has no changes against the epic branch");
|
|
566
597
|
}
|
|
567
|
-
const verdict = yield* judge(story, diff);
|
|
598
|
+
const verdict = yield* judge(story, diff, watchedSeats);
|
|
568
599
|
if (verdict.isClean) {
|
|
569
600
|
judgeNote = `judge cleared (round ${round})`;
|
|
570
601
|
break;
|
|
@@ -584,7 +615,12 @@ export const implementStoriesFlow = Effect.fn("@llm4ts/flow/Stories.implement")(
|
|
|
584
615
|
const changed = yield* git.changedFilesVsBase(epicBranch);
|
|
585
616
|
yield* enforcePerimeter(changed, story);
|
|
586
617
|
const totals = seats.totals === undefined ? undefined : yield* seats.totals;
|
|
587
|
-
|
|
618
|
+
const history = roster === undefined ? [] : yield* roster.history;
|
|
619
|
+
return {
|
|
620
|
+
judge: judgeNote,
|
|
621
|
+
totals,
|
|
622
|
+
executor: history.length === 0 ? undefined : history.join(" → ")
|
|
623
|
+
};
|
|
588
624
|
});
|
|
589
625
|
const runStory = Effect.fn("@llm4ts/flow/Stories.runStory")(function* (story) {
|
|
590
626
|
yield* board.start(story.id);
|
|
@@ -601,13 +637,19 @@ export const implementStoriesFlow = Effect.fn("@llm4ts/flow/Stories.implement")(
|
|
|
601
637
|
}
|
|
602
638
|
const result = yield* Effect.scoped(implementStory(story, state));
|
|
603
639
|
yield* integrate(story, state.branch);
|
|
604
|
-
|
|
640
|
+
const last = result.executor?.split(" → ").at(-1);
|
|
641
|
+
yield* saveVersioned(files, statePath(story), StoryStateVersion, StoryState, StoryState.make({
|
|
642
|
+
...state,
|
|
643
|
+
status: "merged",
|
|
644
|
+
...(last === undefined ? {} : { executor: last })
|
|
645
|
+
}));
|
|
605
646
|
return StoryOutcome.make({
|
|
606
647
|
id: story.id,
|
|
607
648
|
title: story.title,
|
|
608
649
|
status: "done",
|
|
609
650
|
branch: state.branch,
|
|
610
651
|
...(result.judge === undefined ? {} : { judge: result.judge }),
|
|
652
|
+
...(result.executor === undefined ? {} : { executor: result.executor }),
|
|
611
653
|
...(result.totals === undefined ? {} : { estimatedTokens: result.totals.total }),
|
|
612
654
|
...(result.totals?.costUsd === undefined ? {} : { estimatedCostUsd: result.totals.costUsd })
|
|
613
655
|
});
|
|
@@ -620,7 +662,9 @@ export const implementStoriesFlow = Effect.fn("@llm4ts/flow/Stories.implement")(
|
|
|
620
662
|
if (stored !== undefined && stored.status !== "merged") {
|
|
621
663
|
yield* saveVersioned(files, path, StoryStateVersion, StoryState, StoryState.make({ ...stored, status: "failed" })).pipe(Effect.ignore);
|
|
622
664
|
}
|
|
623
|
-
const interruption = error._tag === "EpicCheckoutDirty"
|
|
665
|
+
const interruption = error._tag === "EpicCheckoutDirty" ||
|
|
666
|
+
error._tag === "RosterExhausted" ||
|
|
667
|
+
reason.includes(rosterExhaustedPrefix)
|
|
624
668
|
? "halt"
|
|
625
669
|
: isOutageMessage(reason)
|
|
626
670
|
? "outage"
|
|
@@ -677,7 +721,14 @@ export const implementStoriesFlow = Effect.fn("@llm4ts/flow/Stories.implement")(
|
|
|
677
721
|
if (halted === undefined) {
|
|
678
722
|
const current = yield* progress;
|
|
679
723
|
const ready = readyStories(plan, current);
|
|
680
|
-
|
|
724
|
+
// With a roster, never launch more stories than there are free
|
|
725
|
+
// coders; with nothing running, launch one anyway — its lease waits
|
|
726
|
+
// for the first executor to come back (ADR 0019).
|
|
727
|
+
const free = context.roster === undefined
|
|
728
|
+
? Number.POSITIVE_INFINITY
|
|
729
|
+
: yield* context.roster.available("coder");
|
|
730
|
+
const budget = Math.min(concurrency - current.running.size, free);
|
|
731
|
+
const slots = current.running.size === 0 && ready.length > 0 ? Math.max(1, budget) : budget;
|
|
681
732
|
for (const story of ready.slice(0, Math.max(0, slots))) {
|
|
682
733
|
yield* Ref.update(running, (set) => new Set([...set, story.id]));
|
|
683
734
|
yield* Effect.forkScoped(attempt(story).pipe(Effect.flatMap((completion) => Queue.offer(completions, completion))));
|
|
@@ -718,7 +769,16 @@ export const implementStoriesFlow = Effect.fn("@llm4ts/flow/Stories.implement")(
|
|
|
718
769
|
if (outcome.status === "done") {
|
|
719
770
|
yield* board.complete(outcome.id, {
|
|
720
771
|
...(outcome.branch === undefined ? {} : { branch: outcome.branch }),
|
|
721
|
-
...(outcome.judge === undefined
|
|
772
|
+
...(outcome.judge === undefined && outcome.executor === undefined
|
|
773
|
+
? {}
|
|
774
|
+
: {
|
|
775
|
+
detail: [
|
|
776
|
+
outcome.judge,
|
|
777
|
+
outcome.executor === undefined ? undefined : `coder ${outcome.executor}`
|
|
778
|
+
]
|
|
779
|
+
.filter((part) => part !== undefined)
|
|
780
|
+
.join(" · ")
|
|
781
|
+
}),
|
|
722
782
|
...(outcome.estimatedTokens === undefined
|
|
723
783
|
? {}
|
|
724
784
|
: { estimatedTokens: outcome.estimatedTokens }),
|