@zachwill/pi-orchestrate 0.9.0 → 0.10.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/extension/catalog/definition.ts +89 -0
- package/extension/{catalog.ts → catalog/discovery.ts} +4 -4
- package/extension/index.ts +22 -51
- package/extension/orchestration/admission.ts +297 -0
- package/extension/{domain.ts → orchestration/model.ts} +27 -89
- package/extension/{runtime.ts → orchestration/service.ts} +222 -523
- package/extension/{worker-settlement.ts → orchestration/settlement.ts} +67 -21
- package/extension/package-root.ts +3 -0
- package/extension/{contract.ts → parent/contract.ts} +75 -9
- package/extension/{delivery.ts → parent/delivery.ts} +19 -16
- package/extension/parent/dispatch-policy.ts +49 -0
- package/extension/{host.ts → parent/process-host.ts} +30 -31
- package/extension/{presentation.ts → pi/presentation.ts} +41 -38
- package/extension/pi/tool-renderer.ts +422 -0
- package/extension/pi/tools.ts +470 -0
- package/extension/worker/child-sessions.ts +291 -0
- package/extension/{worker-session.ts → worker/session.ts} +23 -287
- package/package.json +2 -1
- package/extension/tools.ts +0 -771
- /package/extension/{tui.ts → pi/tui.ts} +0 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Context,
|
|
3
|
+
Deferred,
|
|
4
|
+
Effect,
|
|
5
|
+
Exit,
|
|
6
|
+
FiberSet,
|
|
7
|
+
Layer,
|
|
8
|
+
Schema,
|
|
9
|
+
SynchronizedRef,
|
|
10
|
+
} from "effect";
|
|
11
|
+
import {
|
|
12
|
+
createWorkerSession,
|
|
13
|
+
type ChildSessionOptions,
|
|
14
|
+
type WorkerSessionCreationError,
|
|
15
|
+
type WorkerSessionDependencies,
|
|
16
|
+
type WorkerSessionHandle,
|
|
17
|
+
} from "./session.js";
|
|
18
|
+
|
|
19
|
+
export class WorkerSessionAcquisitionClosedError extends Schema.TaggedError<WorkerSessionAcquisitionClosedError>()(
|
|
20
|
+
"WorkerSession.AcquisitionClosedError",
|
|
21
|
+
{ message: Schema.String },
|
|
22
|
+
) {}
|
|
23
|
+
|
|
24
|
+
export type WorkerSessionAcquisitionError =
|
|
25
|
+
| WorkerSessionCreationError
|
|
26
|
+
| WorkerSessionAcquisitionClosedError;
|
|
27
|
+
|
|
28
|
+
export interface ChildSessionsService {
|
|
29
|
+
/**
|
|
30
|
+
* Acquires a child session through a process-owned producer. The adopter must
|
|
31
|
+
* synchronously install ownership before returning a value. Returning undefined
|
|
32
|
+
* rejects adoption and leaves ChildSessions responsible for disposal.
|
|
33
|
+
*/
|
|
34
|
+
readonly acquire: <Adopted>(
|
|
35
|
+
options: ChildSessionOptions,
|
|
36
|
+
adopt: (session: WorkerSessionHandle) => Adopted | undefined,
|
|
37
|
+
) => Effect.Effect<Adopted | undefined, WorkerSessionAcquisitionError>;
|
|
38
|
+
/** Closes every handoff without waiting for uncancellable Pi calls. */
|
|
39
|
+
readonly shutdown: () => Effect.Effect<void>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class ChildSessions extends Context.Service<ChildSessions, ChildSessionsService>()(
|
|
43
|
+
"@zachwill/pi-orchestrate/ChildSessions",
|
|
44
|
+
) {}
|
|
45
|
+
|
|
46
|
+
type AcquisitionHandoffState =
|
|
47
|
+
| { readonly _tag: "Pending" }
|
|
48
|
+
| { readonly _tag: "Offered"; readonly session: WorkerSessionHandle }
|
|
49
|
+
| { readonly _tag: "Adopting"; readonly session: WorkerSessionHandle }
|
|
50
|
+
| { readonly _tag: "Adopted" }
|
|
51
|
+
| { readonly _tag: "Abandoned"; readonly error?: WorkerSessionAcquisitionClosedError }
|
|
52
|
+
| { readonly _tag: "Failed" };
|
|
53
|
+
|
|
54
|
+
interface AcquisitionHandoff {
|
|
55
|
+
readonly state: SynchronizedRef.SynchronizedRef<AcquisitionHandoffState>;
|
|
56
|
+
readonly result: Deferred.Deferred<WorkerSessionHandle, WorkerSessionAcquisitionError>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type ChildSessionsState =
|
|
60
|
+
| { readonly _tag: "Open"; readonly handoffs: ReadonlySet<AcquisitionHandoff> }
|
|
61
|
+
| { readonly _tag: "Closed"; readonly error: WorkerSessionAcquisitionClosedError };
|
|
62
|
+
|
|
63
|
+
type AdoptionReservation =
|
|
64
|
+
| { readonly _tag: "Reserved"; readonly session: WorkerSessionHandle }
|
|
65
|
+
| { readonly _tag: "Closed"; readonly error: WorkerSessionAcquisitionClosedError }
|
|
66
|
+
| { readonly _tag: "Abandoned" };
|
|
67
|
+
|
|
68
|
+
function disposeLateSession(session: WorkerSessionHandle): Effect.Effect<void> {
|
|
69
|
+
return session.dispose().pipe(Effect.catchCause(() => Effect.void));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function admitReclamationOrJoinAfterClosure(
|
|
73
|
+
fibers: FiberSet.FiberSet<void, never>,
|
|
74
|
+
reclamation: Effect.Effect<void>,
|
|
75
|
+
onOpenObserved: () => Effect.Effect<void>,
|
|
76
|
+
): Effect.Effect<void> {
|
|
77
|
+
return Effect.suspend(() => {
|
|
78
|
+
if (fibers.state._tag === "Closed") return reclamation;
|
|
79
|
+
return Effect.gen(function* () {
|
|
80
|
+
yield* onOpenObserved().pipe(Effect.catchCause(() => Effect.void));
|
|
81
|
+
yield* FiberSet.run(fibers, reclamation, { startImmediately: true });
|
|
82
|
+
if (fibers.state._tag === "Closed") {
|
|
83
|
+
// FiberSet.run returns an interrupted sentinel when closure wins admission.
|
|
84
|
+
// If admission won, closure interrupts the admitted fiber instead. Real
|
|
85
|
+
// session disposal is cached and uninterruptible, so this fallback safely
|
|
86
|
+
// joins that same disposal in both cases rather than starting cleanup twice.
|
|
87
|
+
yield* reclamation;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function createChildSessionsLayer(
|
|
94
|
+
overrides: Partial<WorkerSessionDependencies> = {},
|
|
95
|
+
): Layer.Layer<ChildSessions> {
|
|
96
|
+
return Layer.effect(
|
|
97
|
+
ChildSessions,
|
|
98
|
+
Effect.gen(function* () {
|
|
99
|
+
const dependencies = {
|
|
100
|
+
beforeAdoptionReservation:
|
|
101
|
+
overrides.beforeAdoptionReservation ?? (() => Effect.void),
|
|
102
|
+
onReclamationOpenObserved:
|
|
103
|
+
overrides.onReclamationOpenObserved ?? (() => Effect.void),
|
|
104
|
+
};
|
|
105
|
+
const fibers = yield* FiberSet.make<void, never>();
|
|
106
|
+
const serviceState = yield* SynchronizedRef.make<ChildSessionsState>({
|
|
107
|
+
_tag: "Open",
|
|
108
|
+
handoffs: new Set(),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const withoutHandoff = (
|
|
112
|
+
state: ChildSessionsState,
|
|
113
|
+
handoff: AcquisitionHandoff,
|
|
114
|
+
): ChildSessionsState => {
|
|
115
|
+
if (state._tag === "Closed" || !state.handoffs.has(handoff)) return state;
|
|
116
|
+
const handoffs = new Set(state.handoffs);
|
|
117
|
+
handoffs.delete(handoff);
|
|
118
|
+
return { _tag: "Open", handoffs };
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const removeHandoff = (handoff: AcquisitionHandoff): Effect.Effect<void> =>
|
|
122
|
+
SynchronizedRef.update(serviceState, (state) => withoutHandoff(state, handoff));
|
|
123
|
+
|
|
124
|
+
const runReclamation = (session: WorkerSessionHandle): Effect.Effect<void> =>
|
|
125
|
+
admitReclamationOrJoinAfterClosure(
|
|
126
|
+
fibers,
|
|
127
|
+
disposeLateSession(session).pipe(Effect.uninterruptible),
|
|
128
|
+
dependencies.onReclamationOpenObserved,
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const abandonHandoff = Effect.fn("ChildSessions.abandonHandoff")(function* (
|
|
132
|
+
handoff: AcquisitionHandoff,
|
|
133
|
+
) {
|
|
134
|
+
const session = yield* SynchronizedRef.modifyEffect(serviceState, (service) => {
|
|
135
|
+
const nextService = withoutHandoff(service, handoff);
|
|
136
|
+
return SynchronizedRef.modify(handoff.state, (state): readonly [
|
|
137
|
+
{ readonly session: WorkerSessionHandle | undefined },
|
|
138
|
+
AcquisitionHandoffState,
|
|
139
|
+
] => {
|
|
140
|
+
if (state._tag === "Pending") {
|
|
141
|
+
return [{ session: undefined }, { _tag: "Abandoned" }];
|
|
142
|
+
}
|
|
143
|
+
if (state._tag === "Offered") {
|
|
144
|
+
return [{ session: state.session }, { _tag: "Abandoned" }];
|
|
145
|
+
}
|
|
146
|
+
return [{ session: undefined }, state];
|
|
147
|
+
}).pipe(Effect.map(({ session }) => [session, nextService] as const));
|
|
148
|
+
});
|
|
149
|
+
if (session) yield* runReclamation(session);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const shutdown = Effect.fn("ChildSessions.shutdown")(() =>
|
|
153
|
+
Effect.gen(function* () {
|
|
154
|
+
const closed = yield* SynchronizedRef.modifyEffect(serviceState, (state) => {
|
|
155
|
+
if (state._tag === "Closed") return Effect.succeed([undefined, state] as const);
|
|
156
|
+
const error = new WorkerSessionAcquisitionClosedError({
|
|
157
|
+
message: "Child sessions are shutting down",
|
|
158
|
+
});
|
|
159
|
+
return Effect.gen(function* () {
|
|
160
|
+
const sessions: WorkerSessionHandle[] = [];
|
|
161
|
+
for (const handoff of state.handoffs) {
|
|
162
|
+
const session = yield* SynchronizedRef.modifyEffect(handoff.state, (handoffState) => {
|
|
163
|
+
if (handoffState._tag === "Pending") {
|
|
164
|
+
return Deferred.fail(handoff.result, error).pipe(
|
|
165
|
+
Effect.as([undefined, { _tag: "Abandoned", error }] as const),
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
if (handoffState._tag === "Offered") {
|
|
169
|
+
return Effect.succeed([
|
|
170
|
+
handoffState.session,
|
|
171
|
+
{ _tag: "Abandoned", error },
|
|
172
|
+
] as const);
|
|
173
|
+
}
|
|
174
|
+
// Failed already settled its Deferred. Adopting is an ownership
|
|
175
|
+
// reservation whose synchronous winner must be allowed to commit.
|
|
176
|
+
return Effect.succeed([undefined, handoffState] as const);
|
|
177
|
+
});
|
|
178
|
+
if (session) sessions.push(session);
|
|
179
|
+
}
|
|
180
|
+
return [{ sessions }, { _tag: "Closed", error }] as const;
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
if (!closed) return;
|
|
184
|
+
for (const session of closed.sessions) yield* runReclamation(session);
|
|
185
|
+
}).pipe(Effect.uninterruptible)
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
yield* Effect.addFinalizer(() => shutdown());
|
|
189
|
+
|
|
190
|
+
const acquire = Effect.fn("ChildSessions.acquire")(function* <Adopted>(
|
|
191
|
+
options: ChildSessionOptions,
|
|
192
|
+
adopt: (session: WorkerSessionHandle) => Adopted | undefined,
|
|
193
|
+
) {
|
|
194
|
+
const handoff: AcquisitionHandoff = {
|
|
195
|
+
state: yield* SynchronizedRef.make<AcquisitionHandoffState>({ _tag: "Pending" }),
|
|
196
|
+
result: yield* Deferred.make<WorkerSessionHandle, WorkerSessionAcquisitionError>(),
|
|
197
|
+
};
|
|
198
|
+
return yield* Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
|
|
199
|
+
const admissionError = yield* SynchronizedRef.modify(serviceState, (state) => {
|
|
200
|
+
if (state._tag === "Closed") return [state.error, state] as const;
|
|
201
|
+
return [undefined, {
|
|
202
|
+
_tag: "Open",
|
|
203
|
+
handoffs: new Set([...state.handoffs, handoff]),
|
|
204
|
+
}] as const;
|
|
205
|
+
});
|
|
206
|
+
if (admissionError) return yield* Effect.fail(admissionError);
|
|
207
|
+
|
|
208
|
+
const producer = createWorkerSession(options, overrides).pipe(
|
|
209
|
+
Effect.matchCauseEffect({
|
|
210
|
+
onFailure: (cause) => Effect.gen(function* () {
|
|
211
|
+
const failed = yield* SynchronizedRef.modifyEffect(handoff.state, (state) =>
|
|
212
|
+
state._tag === "Pending"
|
|
213
|
+
? Deferred.failCause(handoff.result, cause).pipe(
|
|
214
|
+
Effect.as([true, { _tag: "Failed" }] as const),
|
|
215
|
+
)
|
|
216
|
+
: Effect.succeed([false, state] as const));
|
|
217
|
+
if (failed) yield* removeHandoff(handoff);
|
|
218
|
+
}),
|
|
219
|
+
onSuccess: (session) => Effect.gen(function* () {
|
|
220
|
+
const offered = yield* SynchronizedRef.modifyEffect(handoff.state, (state) =>
|
|
221
|
+
state._tag === "Pending"
|
|
222
|
+
? Deferred.succeed(handoff.result, session).pipe(
|
|
223
|
+
Effect.as([true, { _tag: "Offered", session }] as const),
|
|
224
|
+
)
|
|
225
|
+
: Effect.succeed([false, state] as const));
|
|
226
|
+
if (!offered) {
|
|
227
|
+
yield* removeHandoff(handoff);
|
|
228
|
+
yield* runReclamation(session);
|
|
229
|
+
}
|
|
230
|
+
}),
|
|
231
|
+
}),
|
|
232
|
+
Effect.uninterruptible,
|
|
233
|
+
);
|
|
234
|
+
yield* FiberSet.run(fibers, producer, { startImmediately: true });
|
|
235
|
+
|
|
236
|
+
const session = yield* restore(
|
|
237
|
+
Deferred.await(handoff.result).pipe(
|
|
238
|
+
Effect.tap(() => dependencies.beforeAdoptionReservation()),
|
|
239
|
+
),
|
|
240
|
+
).pipe(Effect.onInterrupt(() => abandonHandoff(handoff)));
|
|
241
|
+
const reservation = yield* SynchronizedRef.modifyEffect(serviceState, (service) => {
|
|
242
|
+
if (service._tag === "Closed") {
|
|
243
|
+
return Effect.succeed([
|
|
244
|
+
{ _tag: "Closed", error: service.error } satisfies AdoptionReservation,
|
|
245
|
+
service,
|
|
246
|
+
] as const);
|
|
247
|
+
}
|
|
248
|
+
return SynchronizedRef.modify(
|
|
249
|
+
handoff.state,
|
|
250
|
+
(state): readonly [AdoptionReservation, AcquisitionHandoffState] => {
|
|
251
|
+
if (state._tag === "Abandoned") {
|
|
252
|
+
return state.error
|
|
253
|
+
? [{ _tag: "Closed", error: state.error }, state]
|
|
254
|
+
: [{ _tag: "Abandoned" }, state];
|
|
255
|
+
}
|
|
256
|
+
if (state._tag !== "Offered" || state.session !== session) {
|
|
257
|
+
return [{ _tag: "Abandoned" }, state];
|
|
258
|
+
}
|
|
259
|
+
return [
|
|
260
|
+
{ _tag: "Reserved", session },
|
|
261
|
+
{ _tag: "Adopting", session },
|
|
262
|
+
];
|
|
263
|
+
},
|
|
264
|
+
).pipe(Effect.map((result) => [result, service] as const));
|
|
265
|
+
});
|
|
266
|
+
if (reservation._tag === "Closed") return yield* Effect.fail(reservation.error);
|
|
267
|
+
if (reservation._tag === "Abandoned") {
|
|
268
|
+
return yield* Effect.die(new Error("Child session acquisition handoff was abandoned"));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// The adopter is arbitrary synchronous runtime code. The reservation
|
|
272
|
+
// protects it from shutdown, but no SynchronizedRef semaphore is held.
|
|
273
|
+
const adopted = yield* Effect.exit(Effect.sync(() => adopt(reservation.session)));
|
|
274
|
+
const transferred = Exit.isSuccess(adopted) && adopted.value !== undefined;
|
|
275
|
+
yield* SynchronizedRef.update(handoff.state, (state): AcquisitionHandoffState => {
|
|
276
|
+
if (state._tag !== "Adopting" || state.session !== reservation.session) return state;
|
|
277
|
+
return transferred ? { _tag: "Adopted" } : { _tag: "Abandoned" };
|
|
278
|
+
});
|
|
279
|
+
yield* removeHandoff(handoff);
|
|
280
|
+
if (transferred) return adopted.value;
|
|
281
|
+
|
|
282
|
+
yield* runReclamation(reservation.session);
|
|
283
|
+
if (Exit.isFailure(adopted)) return yield* Effect.failCause(adopted.cause);
|
|
284
|
+
return undefined;
|
|
285
|
+
}));
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
return ChildSessions.of({ acquire, shutdown });
|
|
289
|
+
}),
|
|
290
|
+
);
|
|
291
|
+
}
|