@abelxiaoxing/cadence 1.0.13 → 1.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.
Files changed (46) hide show
  1. package/README.md +29 -13
  2. package/agents/contract-reviewer.md +2 -2
  3. package/agents/design-explorer.md +1 -1
  4. package/agents/diagnosis-worker.md +6 -3
  5. package/agents/implementation-worker.md +14 -9
  6. package/config/.env.example +5 -17
  7. package/config/routes.example.json +42 -0
  8. package/package.json +3 -3
  9. package/prompts/abel-design.md +150 -228
  10. package/prompts/abel-diagnose.md +37 -18
  11. package/prompts/abel-implement.md +111 -178
  12. package/prompts/abel-init.md +36 -33
  13. package/skills/abel-workflow/SKILL.md +156 -168
  14. package/src/apply-transaction.ts +1427 -0
  15. package/src/artifact-store.ts +204 -0
  16. package/src/child-session.ts +136 -17
  17. package/src/contracts.ts +105 -696
  18. package/src/control-contracts.ts +259 -0
  19. package/src/delivery-compiler.ts +1088 -0
  20. package/src/file-snapshot.ts +5 -0
  21. package/src/implement-graph.ts +11 -4
  22. package/src/index.ts +1465 -133
  23. package/src/isolation-backend.ts +269 -0
  24. package/src/packet-runtime.ts +533 -0
  25. package/src/parent-provider.ts +23 -4
  26. package/src/route-policy.ts +483 -0
  27. package/src/run-state.ts +393 -0
  28. package/src/run-store.ts +918 -0
  29. package/src/safe-path.ts +11 -0
  30. package/src/scoped-tools.ts +36 -0
  31. package/src/state-root.ts +215 -0
  32. package/src/subagent-activity.ts +526 -27
  33. package/src/submit-tool.ts +274 -0
  34. package/src/task-ledger.ts +1380 -0
  35. package/src/verification-capability.ts +529 -42
  36. package/src/worker-broker.ts +703 -0
  37. package/src/workflow-engine.ts +7391 -0
  38. package/src/workspace-store.ts +544 -0
  39. package/src/candidate-preflight.ts +0 -1618
  40. package/src/drain.ts +0 -20
  41. package/src/patch.ts +0 -505
  42. package/src/result-store.ts +0 -312
  43. package/src/runtime.ts +0 -2436
  44. package/src/scheduler.ts +0 -384
  45. package/src/subagent-endpoint.ts +0 -158
  46. package/src/worker.ts +0 -204
@@ -0,0 +1,533 @@
1
+ import path from "node:path";
2
+ import type { Usage } from "@earendil-works/pi-ai";
3
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
4
+ import { Activation, type ActivationState } from "./activation.ts";
5
+ import { loadAgentDefinitions } from "./agent-registry.ts";
6
+ import {
7
+ type ChildFailureKind,
8
+ type ChildSessionResult,
9
+ runChildSession,
10
+ UsageAggregator,
11
+ } from "./child-session.ts";
12
+ import {
13
+ type ChildFailure,
14
+ LIMITS,
15
+ type PacketEnvelope,
16
+ validatePacketEnvelope,
17
+ } from "./contracts.ts";
18
+ import type { ParentPayloadBridge } from "./parent-payload-bridge.ts";
19
+ import { runtimeForWorkerRoute } from "./parent-provider.ts";
20
+ import {
21
+ loadRoutePolicy,
22
+ type RoutePolicy,
23
+ unavailableRoutePolicy,
24
+ type WorkerRole,
25
+ } from "./route-policy.ts";
26
+ import { type BrokerActivityUpdate, RunWorkerBroker } from "./worker-broker.ts";
27
+
28
+ export const PACKET_ACTIONS = ["run", "cancel", "finish"] as const;
29
+ export type PacketAction = (typeof PACKET_ACTIONS)[number];
30
+
31
+ export type PacketActivityState =
32
+ | "queued"
33
+ | "connecting"
34
+ | "waiting-first-response"
35
+ | "running"
36
+ | "retrying"
37
+ | "completed"
38
+ | "failed"
39
+ | "cancelled"
40
+ | "timed-out";
41
+
42
+ export type PacketFailureReason =
43
+ | "subagent failed"
44
+ | "subagent cancelled"
45
+ | "phase timed out";
46
+
47
+ export interface PacketActivityEvent {
48
+ state: PacketActivityState;
49
+ requestId: string;
50
+ role: string;
51
+ phase: string;
52
+ objective: string;
53
+ sequence: number;
54
+ attempt?: number;
55
+ maxAttempts?: number;
56
+ code?: string;
57
+ wait?: string;
58
+ failureReason?: PacketFailureReason;
59
+ }
60
+
61
+ export type PacketActivityObserver = (
62
+ event: PacketActivityEvent,
63
+ ) => void | Promise<void>;
64
+
65
+ export type PacketDispatchResult =
66
+ | {
67
+ ok: true;
68
+ action: PacketAction;
69
+ result?: unknown;
70
+ usage?: Usage;
71
+ }
72
+ | {
73
+ ok: false;
74
+ notReady?: true;
75
+ error: string;
76
+ failure?: ChildFailure;
77
+ usage?: Usage;
78
+ };
79
+
80
+ export interface PacketRuntimeOptions {
81
+ activation?: Activation;
82
+ parentPayloadBridge: ParentPayloadBridge;
83
+ routePolicy?: RoutePolicy;
84
+ routePolicyHome?: string;
85
+ environment?: Readonly<Record<string, string | undefined>>;
86
+ concurrency?: number;
87
+ childRunner?: PacketChildRunner;
88
+ }
89
+
90
+ export type PacketContext = Pick<
91
+ ExtensionContext,
92
+ "cwd" | "model" | "modelRegistry"
93
+ >;
94
+
95
+ export type PacketChildRunner = (input: {
96
+ packet: PacketEnvelope;
97
+ context: PacketContext;
98
+ signal: AbortSignal;
99
+ }) => Promise<ChildSessionResult>;
100
+
101
+ interface WaitingPacket {
102
+ controller: AbortController;
103
+ resolve(acquired: boolean): void;
104
+ onAbort(): void;
105
+ }
106
+
107
+ function cancelledResult(signal: AbortSignal): PacketDispatchResult {
108
+ const reason = signal.reason;
109
+ return {
110
+ ok: false,
111
+ error:
112
+ reason instanceof Error
113
+ ? reason.message
114
+ : typeof reason === "string"
115
+ ? reason
116
+ : "packet cancelled",
117
+ failure: { kind: "cancelled", code: "cancelled" },
118
+ };
119
+ }
120
+
121
+ function failureState(kind: ChildFailureKind): PacketActivityState {
122
+ return kind === "cancelled"
123
+ ? "cancelled"
124
+ : kind === "timed-out"
125
+ ? "timed-out"
126
+ : "failed";
127
+ }
128
+
129
+ function syntheticChildFailure(
130
+ error: string,
131
+ failure: ChildFailure,
132
+ failureKind: ChildFailureKind = failure.kind === "cancelled"
133
+ ? "cancelled"
134
+ : "failed",
135
+ ): ChildSessionResult {
136
+ return {
137
+ ok: false,
138
+ error,
139
+ failure,
140
+ failureKind,
141
+ transportFailure: failure.kind === "transport",
142
+ disposeCount: 0,
143
+ usage: new UsageAggregator().total(),
144
+ classification: {
145
+ finalCategory: "no-final-assistant",
146
+ attempts: 0,
147
+ schema: "invalid",
148
+ identity: { request: false, role: false, task: false, phase: false },
149
+ },
150
+ };
151
+ }
152
+
153
+ export class PacketRuntime {
154
+ readonly activation: Activation;
155
+ readonly limits = LIMITS;
156
+ readonly #parentPayloadBridge: ParentPayloadBridge;
157
+ readonly #routePolicy?: RoutePolicy;
158
+ readonly #routePolicyHome?: string;
159
+ readonly #environment: Readonly<Record<string, string | undefined>>;
160
+ readonly #concurrency: number;
161
+ readonly #childRunner?: PacketChildRunner;
162
+ readonly #brokers = new Map<string, RunWorkerBroker>();
163
+ readonly #brokerPolicies = new Map<string, string>();
164
+ readonly #controllers = new Set<AbortController>();
165
+ readonly #operations = new Set<Promise<PacketDispatchResult>>();
166
+ readonly #waiting: WaitingPacket[] = [];
167
+ #active = 0;
168
+ #sequence = 0;
169
+
170
+ constructor(options: PacketRuntimeOptions) {
171
+ this.activation = options.activation ?? new Activation();
172
+ this.#parentPayloadBridge = options.parentPayloadBridge;
173
+ this.#routePolicy = options.routePolicy;
174
+ this.#routePolicyHome = options.routePolicyHome;
175
+ this.#environment = options.environment ?? process.env;
176
+ this.#childRunner = options.childRunner;
177
+ this.#concurrency = Math.max(
178
+ 1,
179
+ Math.min(
180
+ options.concurrency ?? LIMITS.maxActiveChildSessions,
181
+ LIMITS.maxActiveChildSessions,
182
+ ),
183
+ );
184
+ }
185
+
186
+ validateRequest(value: unknown) {
187
+ return validatePacketEnvelope(value);
188
+ }
189
+
190
+ async execute(
191
+ action: string,
192
+ params: { request?: unknown },
193
+ context?: PacketContext,
194
+ signal?: AbortSignal,
195
+ observer?: PacketActivityObserver,
196
+ ): Promise<PacketDispatchResult> {
197
+ if (!(PACKET_ACTIONS as readonly string[]).includes(action)) {
198
+ throw new Error(`unknown packet action: ${String(action)}`);
199
+ }
200
+ if (!this.activation.isActive()) {
201
+ return { ok: false, notReady: true, error: "dispatcher is not active" };
202
+ }
203
+ if (action === "cancel") {
204
+ await this.cancel();
205
+ return { ok: true, action };
206
+ }
207
+ if (action === "finish") {
208
+ await this.drain();
209
+ return { ok: true, action };
210
+ }
211
+ const operation = this.#run(params.request, context, signal, observer);
212
+ this.#operations.add(operation);
213
+ try {
214
+ return await operation;
215
+ } finally {
216
+ this.#operations.delete(operation);
217
+ }
218
+ }
219
+
220
+ async #run(
221
+ request: unknown,
222
+ context?: PacketContext,
223
+ parentSignal?: AbortSignal,
224
+ observer?: PacketActivityObserver,
225
+ ): Promise<PacketDispatchResult> {
226
+ if (!context) return { ok: false, error: "run requires extension context" };
227
+ const validation = validatePacketEnvelope(request);
228
+ if (!validation.ok) return { ok: false, error: validation.reason };
229
+ const packet = validation.value;
230
+ const sequence = ++this.#sequence;
231
+ this.#notify(packet, observer, sequence, "queued");
232
+
233
+ const controller = new AbortController();
234
+ this.#controllers.add(controller);
235
+ const forwardCancellation = () => {
236
+ if (!controller.signal.aborted) controller.abort(parentSignal?.reason);
237
+ };
238
+ if (parentSignal?.aborted) forwardCancellation();
239
+ else
240
+ parentSignal?.addEventListener("abort", forwardCancellation, {
241
+ once: true,
242
+ });
243
+
244
+ let acquired = false;
245
+ try {
246
+ acquired = await this.#acquire(controller);
247
+ if (!acquired || controller.signal.aborted) {
248
+ this.#notify(packet, observer, sequence, "cancelled");
249
+ return cancelledResult(controller.signal);
250
+ }
251
+ if (this.#childRunner) {
252
+ this.#notify(packet, observer, sequence, "running");
253
+ }
254
+ const usage = new UsageAggregator();
255
+ const child = await this.#dispatchChild(
256
+ packet,
257
+ context,
258
+ controller.signal,
259
+ (activity) =>
260
+ this.#notifyBrokerActivity(packet, observer, sequence, activity),
261
+ );
262
+ usage.add("launch:0", child.usage);
263
+ if (!child.ok) {
264
+ const state = failureState(child.failureKind);
265
+ this.#notify(packet, observer, sequence, state);
266
+ return {
267
+ ok: false,
268
+ error: child.error,
269
+ failure: child.failure,
270
+ usage: usage.total(),
271
+ };
272
+ }
273
+ this.#notify(packet, observer, sequence, "completed");
274
+ return {
275
+ ok: true,
276
+ action: "run",
277
+ result: child.result,
278
+ usage: usage.total(),
279
+ };
280
+ } catch (error) {
281
+ this.#notify(
282
+ packet,
283
+ observer,
284
+ sequence,
285
+ controller.signal.aborted ? "cancelled" : "failed",
286
+ );
287
+ throw error;
288
+ } finally {
289
+ parentSignal?.removeEventListener("abort", forwardCancellation);
290
+ this.#controllers.delete(controller);
291
+ if (acquired) this.#release();
292
+ }
293
+ }
294
+
295
+ async #dispatchChild(
296
+ packet: PacketEnvelope,
297
+ context: PacketContext,
298
+ signal: AbortSignal,
299
+ onActivity?: (event: BrokerActivityUpdate) => void,
300
+ ): Promise<ChildSessionResult> {
301
+ if (signal.aborted) {
302
+ const result = cancelledResult(signal);
303
+ return syntheticChildFailure(
304
+ result.ok ? "packet cancelled" : result.error,
305
+ { kind: "cancelled", code: "cancelled" },
306
+ "cancelled",
307
+ );
308
+ }
309
+ if (this.#childRunner) {
310
+ return this.#childRunner({
311
+ packet: structuredClone(packet),
312
+ context,
313
+ signal,
314
+ });
315
+ }
316
+ const agent = loadAgentDefinitions().find(
317
+ (candidate) => candidate.role === packet.role,
318
+ );
319
+ if (!agent) {
320
+ throw new Error("packet agent definition is unavailable");
321
+ }
322
+ const broker = this.#brokerFor(context.cwd);
323
+ const routed = await broker.run({
324
+ runId: `${packet.stage}:${packet.id}`,
325
+ operationId: packet.id,
326
+ role: packet.role as WorkerRole,
327
+ signal,
328
+ ...(onActivity ? { onActivity } : {}),
329
+ execute: async (attempt) => {
330
+ const phase = await runtimeForWorkerRoute(
331
+ attempt.route,
332
+ context,
333
+ this.#parentPayloadBridge,
334
+ attempt.signal,
335
+ this.#environment,
336
+ );
337
+ if (!phase.ok) {
338
+ if (phase.failure.kind === "cancelled") {
339
+ return { kind: "phase-failure" as const, phase };
340
+ }
341
+ throw new Error(phase.failure.code);
342
+ }
343
+ const child = await runChildSession({
344
+ cwd: context.cwd,
345
+ modelRuntime: phase.modelRuntime,
346
+ model: phase.model,
347
+ systemPrompt: [
348
+ agent.content,
349
+ packet.objective,
350
+ packet.context.agents,
351
+ packet.context.contract,
352
+ `<packet-contract>${JSON.stringify({
353
+ requestId: packet.id,
354
+ phase: packet.phase,
355
+ readSet: packet.declared.read,
356
+ writeSet: packet.declared.write,
357
+ output: packet.output,
358
+ })}</packet-contract>`,
359
+ ].join("\n\n"),
360
+ requestId: packet.id,
361
+ role: packet.role,
362
+ phase: packet.phase,
363
+ output: packet.output as "evidence" | "diff",
364
+ roots: packet.roots.map((root) => path.resolve(context.cwd, root)),
365
+ allowedPaths: [
366
+ ...new Set([...packet.declared.read, ...packet.declared.write]),
367
+ ],
368
+ timeoutMs: LIMITS.phaseTimeoutMs,
369
+ signal: attempt.signal,
370
+ failureOverride: phase.failureOverride,
371
+ onStreamStart: attempt.onHeaders,
372
+ onStreamProgress: attempt.onProgress,
373
+ });
374
+ if (!child.ok && child.failure.kind === "transport") {
375
+ throw new Error(child.failure.code);
376
+ }
377
+ return { kind: "child" as const, child };
378
+ },
379
+ });
380
+ if (!routed.ok) {
381
+ const cancelled = routed.state === "cancelled" || signal.aborted;
382
+ const timedOut = !cancelled && routed.code === "phase-timeout";
383
+ return syntheticChildFailure(
384
+ cancelled ? "packet cancelled" : routed.code,
385
+ cancelled
386
+ ? { kind: "cancelled", code: "cancelled" }
387
+ : timedOut
388
+ ? {
389
+ kind: "transport",
390
+ code: "timeout",
391
+ stage: "child-timeout",
392
+ }
393
+ : {
394
+ kind: "transport",
395
+ code: "transport-failure",
396
+ stage: "child-provider-stream",
397
+ },
398
+ cancelled ? "cancelled" : timedOut ? "timed-out" : "failed",
399
+ );
400
+ }
401
+ if (routed.value.kind === "phase-failure") {
402
+ const phase = routed.value.phase;
403
+ return syntheticChildFailure(phase.error, phase.failure);
404
+ }
405
+ return routed.value.child;
406
+ }
407
+
408
+ #brokerFor(cwd: string): RunWorkerBroker {
409
+ const root = path.resolve(cwd);
410
+ const existing = this.#brokers.get(root);
411
+ let policy = this.#routePolicy;
412
+ if (policy && existing) return existing;
413
+ if (!policy) {
414
+ const resolution = loadRoutePolicy({
415
+ cwd: root,
416
+ ...(this.#routePolicyHome ? { home: this.#routePolicyHome } : {}),
417
+ });
418
+ if (!resolution.ok && resolution.source.kind !== "none") {
419
+ const code = resolution.diagnostics[0]?.code ?? "policy-invalid";
420
+ throw new Error(`route-policy-unavailable:${code}`);
421
+ }
422
+ policy = resolution.ok ? resolution.policy : unavailableRoutePolicy();
423
+ }
424
+ const serializedPolicy = JSON.stringify(policy);
425
+ if (existing && this.#brokerPolicies.get(root) === serializedPolicy) {
426
+ return existing;
427
+ }
428
+ const broker = new RunWorkerBroker(policy);
429
+ this.#brokers.set(root, broker);
430
+ this.#brokerPolicies.set(root, serializedPolicy);
431
+ return broker;
432
+ }
433
+
434
+ #notifyBrokerActivity(
435
+ packet: PacketEnvelope,
436
+ observer: PacketActivityObserver | undefined,
437
+ sequence: number,
438
+ activity: BrokerActivityUpdate,
439
+ ): void {
440
+ this.#notify(packet, observer, sequence, activity.state, {
441
+ attempt: activity.attempt,
442
+ maxAttempts: activity.maxAttempts,
443
+ ...(activity.code ? { code: activity.code } : {}),
444
+ ...(activity.wait ? { wait: activity.wait } : {}),
445
+ });
446
+ }
447
+
448
+ #notify(
449
+ packet: PacketEnvelope,
450
+ observer: PacketActivityObserver | undefined,
451
+ sequence: number,
452
+ state: PacketActivityState,
453
+ metadata: Pick<
454
+ PacketActivityEvent,
455
+ "attempt" | "maxAttempts" | "code" | "wait"
456
+ > = {},
457
+ ): void {
458
+ if (!observer) return;
459
+ const event: PacketActivityEvent = {
460
+ state,
461
+ requestId: packet.id,
462
+ role: packet.role,
463
+ phase: packet.phase,
464
+ objective: packet.objective,
465
+ sequence,
466
+ ...metadata,
467
+ ...(state === "cancelled"
468
+ ? { failureReason: "subagent cancelled" as const }
469
+ : state === "timed-out"
470
+ ? { failureReason: "phase timed out" as const }
471
+ : state === "failed"
472
+ ? { failureReason: "subagent failed" as const }
473
+ : {}),
474
+ };
475
+ try {
476
+ const result = observer(event);
477
+ if (result instanceof Promise) void result.catch(() => undefined);
478
+ } catch {
479
+ // Presentation observers cannot alter execution.
480
+ }
481
+ }
482
+
483
+ #acquire(controller: AbortController): Promise<boolean> {
484
+ if (controller.signal.aborted) return Promise.resolve(false);
485
+ if (this.#active < this.#concurrency) {
486
+ this.#active += 1;
487
+ return Promise.resolve(true);
488
+ }
489
+ return new Promise<boolean>((resolve) => {
490
+ let waiting: WaitingPacket;
491
+ const onAbort = () => {
492
+ const index = this.#waiting.indexOf(waiting);
493
+ if (index >= 0) this.#waiting.splice(index, 1);
494
+ resolve(false);
495
+ };
496
+ waiting = { controller, resolve, onAbort };
497
+ this.#waiting.push(waiting);
498
+ controller.signal.addEventListener("abort", onAbort, { once: true });
499
+ });
500
+ }
501
+
502
+ #release(): void {
503
+ this.#active -= 1;
504
+ while (this.#waiting.length > 0) {
505
+ const next = this.#waiting.shift();
506
+ if (!next || next.controller.signal.aborted) continue;
507
+ next.controller.signal.removeEventListener("abort", next.onAbort);
508
+ this.#active += 1;
509
+ next.resolve(true);
510
+ break;
511
+ }
512
+ }
513
+
514
+ async cancel(): Promise<void> {
515
+ const reason = new Error("packet runtime cancelled");
516
+ for (const controller of this.#controllers) {
517
+ if (!controller.signal.aborted) controller.abort(reason);
518
+ }
519
+ await Promise.allSettled([...this.#operations]);
520
+ }
521
+
522
+ async drain(): Promise<void> {
523
+ this.activation.drain();
524
+ await this.cancel();
525
+ this.#brokers.clear();
526
+ this.#brokerPolicies.clear();
527
+ this.#parentPayloadBridge.clear();
528
+ }
529
+
530
+ get state(): ActivationState {
531
+ return this.activation.state;
532
+ }
533
+ }
@@ -20,7 +20,12 @@ import {
20
20
  type ParentPayloadCallback,
21
21
  type ParentPayloadCapture,
22
22
  } from "./parent-payload-bridge.ts";
23
- import type { SubagentEndpoint } from "./subagent-endpoint.ts";
23
+ import {
24
+ type CustomRoutePolicy,
25
+ type InheritedRoutePolicy,
26
+ type ResolvedCustomRoute,
27
+ resolveCustomRoute,
28
+ } from "./route-policy.ts";
24
29
 
25
30
  const SUBAGENT_PROVIDER_ID = "abel-subagent";
26
31
 
@@ -71,7 +76,7 @@ const SUBAGENT_STREAMS = {
71
76
  },
72
77
  };
73
78
 
74
- function customSubagentModel(endpoint: SubagentEndpoint): Model<string> {
79
+ function customSubagentModel(endpoint: ResolvedCustomRoute): Model<string> {
75
80
  return {
76
81
  id: endpoint.model,
77
82
  name: endpoint.model,
@@ -90,7 +95,7 @@ function customSubagentModel(endpoint: SubagentEndpoint): Model<string> {
90
95
  }
91
96
 
92
97
  function customSubagentProvider(
93
- endpoint: SubagentEndpoint,
98
+ endpoint: ResolvedCustomRoute,
94
99
  model: Model<string>,
95
100
  ): Provider {
96
101
  return createProvider({
@@ -118,10 +123,12 @@ function customSubagentProvider(
118
123
  }
119
124
 
120
125
  export async function customPhaseRuntime(
121
- endpoint: SubagentEndpoint,
126
+ route: CustomRoutePolicy,
122
127
  signal?: AbortSignal,
128
+ environment: Readonly<Record<string, string | undefined>> = process.env,
123
129
  ): Promise<PhaseRuntimeResult> {
124
130
  if (signal?.aborted) return cancelledPhaseRuntime();
131
+ const endpoint = resolveCustomRoute(route, environment);
125
132
  const model = customSubagentModel(endpoint);
126
133
  const provider = customSubagentProvider(endpoint, model);
127
134
  let modelRuntime: ModelRuntime;
@@ -419,6 +426,18 @@ export async function runtimeFromContext(
419
426
  };
420
427
  }
421
428
 
429
+ export async function runtimeForWorkerRoute(
430
+ route: CustomRoutePolicy | InheritedRoutePolicy,
431
+ ctx: Pick<ExtensionContext, "model" | "modelRegistry">,
432
+ payloadBridge: ParentPayloadBridge,
433
+ signal?: AbortSignal,
434
+ environment: Readonly<Record<string, string | undefined>> = process.env,
435
+ ): Promise<PhaseRuntimeResult> {
436
+ return route.kind === "custom"
437
+ ? customPhaseRuntime(route, signal, environment)
438
+ : runtimeFromContext(ctx, payloadBridge, signal);
439
+ }
440
+
422
441
  function abortable<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T> {
423
442
  if (!signal) return promise;
424
443
  signal.throwIfAborted();