@swirls/looms 0.2.0 → 0.4.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 (113) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/actor/host.js +4 -6
  3. package/dist/actor/index.d.ts +1 -1
  4. package/dist/actor/index.js +1 -1
  5. package/dist/actor/routing.d.ts +5 -0
  6. package/dist/actor/routing.js +14 -0
  7. package/dist/agent/agent-session.js +1 -0
  8. package/dist/agent/debugger/agent-chat.js +45 -38
  9. package/dist/agent/debugger/streaming-text.d.ts +6 -0
  10. package/dist/agent/debugger/streaming-text.js +33 -0
  11. package/dist/agent/debugger/summarize.js +2 -0
  12. package/dist/agent/effects.d.ts +30 -0
  13. package/dist/agent/events.d.ts +2 -0
  14. package/dist/agent/events.js +2 -0
  15. package/dist/agent/llm.js +1 -8
  16. package/dist/agent/module.d.ts +230 -160
  17. package/dist/agent/module.js +11 -7
  18. package/dist/agent/projections.d.ts +5 -0
  19. package/dist/agent/projections.js +43 -2
  20. package/dist/agent/scope.d.ts +2 -0
  21. package/dist/agent/signals.d.ts +5 -0
  22. package/dist/agent/signals.js +7 -0
  23. package/dist/agent/threads.js +35 -21
  24. package/dist/agent/transcript.d.ts +7 -0
  25. package/dist/agent/transcript.js +19 -0
  26. package/dist/agent/types.d.ts +10 -0
  27. package/dist/agent/types.js +4 -0
  28. package/dist/approval/debugger/approvals-view.js +5 -3
  29. package/dist/approval/effects.d.ts +12 -0
  30. package/dist/approval/module.d.ts +24 -0
  31. package/dist/client/client.d.ts +292 -0
  32. package/dist/client/client.js +17 -1
  33. package/dist/cloudflare/router.d.ts +1 -1
  34. package/dist/cloudflare/router.js +6 -8
  35. package/dist/core/attempts.d.ts +8 -0
  36. package/dist/core/attempts.js +27 -0
  37. package/dist/core/effects.d.ts +7 -0
  38. package/dist/core/effects.js +1 -0
  39. package/dist/core/fold.js +12 -6
  40. package/dist/core/fork.d.ts +35 -0
  41. package/dist/core/fork.js +129 -0
  42. package/dist/core/index.d.ts +3 -0
  43. package/dist/core/index.js +2 -0
  44. package/dist/core/module-scope.d.ts +1 -0
  45. package/dist/core/module.d.ts +2 -2
  46. package/dist/core/projection.d.ts +5 -4
  47. package/dist/core/projection.js +1 -0
  48. package/dist/core/protocol.d.ts +10 -1
  49. package/dist/core/protocol.js +11 -0
  50. package/dist/{react → core}/register.d.ts +9 -8
  51. package/dist/core/replay.d.ts +8 -0
  52. package/dist/core/snapshots.js +3 -17
  53. package/dist/core/state.d.ts +14 -0
  54. package/dist/core/state.js +25 -2
  55. package/dist/core/store.d.ts +1 -0
  56. package/dist/debugger/app/assets/index-CgDzWJg7.css +2 -0
  57. package/dist/debugger/app/assets/index-D--9esm9.js +22 -0
  58. package/dist/debugger/app/assets/routes-Dakrdusf.js +47 -0
  59. package/dist/debugger/app/index.html +2 -2
  60. package/dist/debugger/components/composer.d.ts +5 -2
  61. package/dist/debugger/components/composer.js +5 -4
  62. package/dist/debugger/components/run-tree.js +2 -5
  63. package/dist/debugger/events/catalog.js +1 -3
  64. package/dist/debugger/events/protocol-summarize.js +7 -0
  65. package/dist/debugger/lib/event-counts.d.ts +2 -2
  66. package/dist/debugger/lib/event-counts.js +5 -9
  67. package/dist/debugger/shell/debugger-shell.js +1 -1
  68. package/dist/debugger/shell/run-controls.d.ts +2 -1
  69. package/dist/debugger/shell/run-controls.js +40 -13
  70. package/dist/debugger/shell/run-search.js +1 -3
  71. package/dist/evaluator/effects.d.ts +45 -33
  72. package/dist/evaluator/module.d.ts +90 -66
  73. package/dist/projectors/index-model.d.ts +13 -7
  74. package/dist/projectors/index-model.js +21 -26
  75. package/dist/projectors/memory.js +19 -11
  76. package/dist/projectors/postgres.js +20 -19
  77. package/dist/projectors/schema.js +20 -18
  78. package/dist/projectors/sqlite.js +32 -27
  79. package/dist/react/derived.d.ts +26 -8
  80. package/dist/react/derived.js +23 -26
  81. package/dist/react/index.d.ts +3 -3
  82. package/dist/react/index.js +2 -2
  83. package/dist/react/materialize.js +16 -11
  84. package/dist/react/react.d.ts +12 -9
  85. package/dist/react/react.js +9 -6
  86. package/dist/react/store.d.ts +4 -5
  87. package/dist/react/tables.d.ts +3 -2
  88. package/dist/runtime/cancellation.js +14 -27
  89. package/dist/runtime/errors.d.ts +39 -0
  90. package/dist/runtime/errors.js +32 -0
  91. package/dist/runtime/helpers.d.ts +2 -0
  92. package/dist/runtime/helpers.js +23 -1
  93. package/dist/runtime/index.d.ts +1 -1
  94. package/dist/runtime/index.js +1 -1
  95. package/dist/runtime/looms.d.ts +4 -0
  96. package/dist/runtime/looms.js +1 -0
  97. package/dist/runtime/run-start.js +1 -3
  98. package/dist/runtime/runtime.d.ts +1 -1
  99. package/dist/runtime/runtime.js +45 -18
  100. package/dist/runtime/server/http.d.ts +2 -0
  101. package/dist/runtime/server/http.js +17 -2
  102. package/dist/runtime/server/routes/runs.js +35 -2
  103. package/dist/runtime/types.d.ts +8 -0
  104. package/dist/runtime/wake-engine.js +5 -2
  105. package/dist/s2/snapshot-store.js +2 -6
  106. package/dist/workflow/effects.d.ts +24 -0
  107. package/dist/workflow/effects.js +1 -0
  108. package/dist/workflow/module.d.ts +48 -0
  109. package/package.json +2 -2
  110. package/dist/debugger/app/assets/index-B3Ah8WAE.css +0 -2
  111. package/dist/debugger/app/assets/index-Qb371t_O.js +0 -22
  112. package/dist/debugger/app/assets/routes-CjtEHgPQ.js +0 -47
  113. /package/dist/{react → core}/register.js +0 -0
@@ -37,6 +37,10 @@ export declare function createLoomsClient(options?: LoomsClientOptions): {
37
37
  readonly idempotencyKey: string | null;
38
38
  readonly rootThreadId: string;
39
39
  } | null;
40
+ readonly forkedFrom: {
41
+ readonly runId: string;
42
+ readonly seq: number;
43
+ } | null;
40
44
  readonly threads: {
41
45
  readonly [x: string]: {
42
46
  readonly threadId: string;
@@ -160,6 +164,10 @@ export declare function createLoomsClient(options?: LoomsClientOptions): {
160
164
  readonly idempotencyKey: string | null;
161
165
  readonly rootThreadId: string;
162
166
  } | null;
167
+ readonly forkedFrom: {
168
+ readonly runId: string;
169
+ readonly seq: number;
170
+ } | null;
163
171
  readonly threads: {
164
172
  readonly [x: string]: {
165
173
  readonly threadId: string;
@@ -281,6 +289,10 @@ export declare function createLoomsClient(options?: LoomsClientOptions): {
281
289
  readonly idempotencyKey: string | null;
282
290
  readonly rootThreadId: string;
283
291
  } | null;
292
+ readonly forkedFrom: {
293
+ readonly runId: string;
294
+ readonly seq: number;
295
+ } | null;
284
296
  readonly threads: {
285
297
  readonly [x: string]: {
286
298
  readonly threadId: string;
@@ -402,6 +414,10 @@ export declare function createLoomsClient(options?: LoomsClientOptions): {
402
414
  readonly idempotencyKey: string | null;
403
415
  readonly rootThreadId: string;
404
416
  } | null;
417
+ readonly forkedFrom: {
418
+ readonly runId: string;
419
+ readonly seq: number;
420
+ } | null;
405
421
  readonly threads: {
406
422
  readonly [x: string]: {
407
423
  readonly threadId: string;
@@ -553,6 +569,10 @@ export declare function createLoomsClient(options?: LoomsClientOptions): {
553
569
  readonly idempotencyKey: string | null;
554
570
  readonly rootThreadId: string;
555
571
  } | null;
572
+ readonly forkedFrom: {
573
+ readonly runId: string;
574
+ readonly seq: number;
575
+ } | null;
556
576
  readonly threads: {
557
577
  readonly [x: string]: {
558
578
  readonly threadId: string;
@@ -674,6 +694,266 @@ export declare function createLoomsClient(options?: LoomsClientOptions): {
674
694
  readonly idempotencyKey: string | null;
675
695
  readonly rootThreadId: string;
676
696
  } | null;
697
+ readonly forkedFrom: {
698
+ readonly runId: string;
699
+ readonly seq: number;
700
+ } | null;
701
+ readonly threads: {
702
+ readonly [x: string]: {
703
+ readonly threadId: string;
704
+ readonly kind: string;
705
+ readonly definitionName: string;
706
+ readonly definitionVersion: string;
707
+ readonly parentThreadId: string | null;
708
+ readonly status: "cancelled" | "completed" | "failed" | "running" | "waiting";
709
+ readonly input: Schema.Json;
710
+ readonly output: Schema.Json;
711
+ readonly error: string | null;
712
+ readonly state: Schema.Json;
713
+ };
714
+ };
715
+ readonly waits: {
716
+ readonly [x: string]: {
717
+ readonly waitId: string;
718
+ readonly threadId: string;
719
+ readonly on: {
720
+ readonly type: string | readonly string[];
721
+ readonly match?: Schema.Json | undefined;
722
+ } | {
723
+ readonly timerAt: number;
724
+ };
725
+ readonly tag?: Schema.Json | undefined;
726
+ };
727
+ };
728
+ readonly outstandingEffects: readonly {
729
+ readonly effectId: string;
730
+ readonly threadId: string;
731
+ readonly causingSeq: number;
732
+ readonly causingEventId: string;
733
+ readonly effect: {
734
+ readonly type: "runtime.spawn";
735
+ readonly childThreadId: string;
736
+ readonly kind: string;
737
+ readonly definitionName: string;
738
+ readonly definitionVersion: string;
739
+ readonly input: Schema.Json;
740
+ } | {
741
+ readonly type: "runtime.wait";
742
+ readonly waitId: string;
743
+ readonly on: {
744
+ readonly type: string | readonly string[];
745
+ readonly match?: Schema.Json | undefined;
746
+ } | {
747
+ readonly timerAt: number;
748
+ };
749
+ readonly tag?: Schema.Json | undefined;
750
+ } | {
751
+ readonly type: "runtime.emit";
752
+ readonly event: {
753
+ readonly type: string;
754
+ readonly payload: Schema.Json;
755
+ readonly threadId?: string | null | undefined;
756
+ readonly parentThreadId?: string | null | undefined;
757
+ readonly causationId?: string | null | undefined;
758
+ readonly correlationId?: string | null | undefined;
759
+ readonly effectId?: string | null | undefined;
760
+ readonly origin?: {
761
+ readonly type: "thread";
762
+ readonly threadId: string;
763
+ } | {
764
+ readonly type: "external";
765
+ readonly actorId?: string | undefined;
766
+ } | {
767
+ readonly type: "system";
768
+ } | undefined;
769
+ readonly ephemeral?: boolean | undefined;
770
+ readonly id?: string | undefined;
771
+ readonly ts?: number | undefined;
772
+ readonly idempotencyKey?: string | null | undefined;
773
+ };
774
+ readonly tag?: string | undefined;
775
+ } | {
776
+ readonly type: "runtime.complete";
777
+ readonly output: Schema.Json;
778
+ } | {
779
+ readonly type: "runtime.fail";
780
+ readonly error: string;
781
+ } | {
782
+ readonly type: "runtime.cancel";
783
+ readonly threadId: string;
784
+ readonly tag?: string | undefined;
785
+ } | {
786
+ readonly type: string;
787
+ readonly input: Schema.Json;
788
+ readonly tag?: string | undefined;
789
+ };
790
+ }[];
791
+ readonly effectExecutions: {
792
+ readonly [x: string]: {
793
+ readonly effectId: string;
794
+ readonly attempt: number;
795
+ readonly status: "ambiguous" | "cancel_requested" | "dispatched" | "heartbeat" | "queued" | "retry_wait" | "running" | "started" | "timed_out";
796
+ readonly nextAttemptAt: number | null;
797
+ readonly deadlineAt: number | null;
798
+ readonly startedAt: number | null;
799
+ readonly lastHeartbeatAt: number | null;
800
+ readonly lastError: string | null;
801
+ };
802
+ };
803
+ readonly completedEffectIds: readonly string[];
804
+ readonly processedIdempotencyKeys: readonly string[];
805
+ };
806
+ readonly appended: number;
807
+ }>;
808
+ /** Cancel a thread and its descendants; omit `threadId` to cancel the whole run. */
809
+ cancel: (runId: string, threadId?: string) => Promise<{
810
+ readonly runId: string;
811
+ readonly state: {
812
+ readonly runId: string;
813
+ readonly status: "cancelled" | "completed" | "failed" | "running";
814
+ readonly rootThreadId: string | null;
815
+ readonly startIdentity: {
816
+ readonly kind: string;
817
+ readonly definitionName: string;
818
+ readonly definitionVersion: string;
819
+ readonly input: Schema.Json;
820
+ readonly requestedThreadId: string | null;
821
+ readonly idempotencyKey: string | null;
822
+ readonly rootThreadId: string;
823
+ } | null;
824
+ readonly forkedFrom: {
825
+ readonly runId: string;
826
+ readonly seq: number;
827
+ } | null;
828
+ readonly threads: {
829
+ readonly [x: string]: {
830
+ readonly threadId: string;
831
+ readonly kind: string;
832
+ readonly definitionName: string;
833
+ readonly definitionVersion: string;
834
+ readonly parentThreadId: string | null;
835
+ readonly status: "cancelled" | "completed" | "failed" | "running" | "waiting";
836
+ readonly input: Schema.Json;
837
+ readonly output: Schema.Json;
838
+ readonly error: string | null;
839
+ readonly state: Schema.Json;
840
+ };
841
+ };
842
+ readonly waits: {
843
+ readonly [x: string]: {
844
+ readonly waitId: string;
845
+ readonly threadId: string;
846
+ readonly on: {
847
+ readonly type: string | readonly string[];
848
+ readonly match?: Schema.Json | undefined;
849
+ } | {
850
+ readonly timerAt: number;
851
+ };
852
+ readonly tag?: Schema.Json | undefined;
853
+ };
854
+ };
855
+ readonly outstandingEffects: readonly {
856
+ readonly effectId: string;
857
+ readonly threadId: string;
858
+ readonly causingSeq: number;
859
+ readonly causingEventId: string;
860
+ readonly effect: {
861
+ readonly type: "runtime.spawn";
862
+ readonly childThreadId: string;
863
+ readonly kind: string;
864
+ readonly definitionName: string;
865
+ readonly definitionVersion: string;
866
+ readonly input: Schema.Json;
867
+ } | {
868
+ readonly type: "runtime.wait";
869
+ readonly waitId: string;
870
+ readonly on: {
871
+ readonly type: string | readonly string[];
872
+ readonly match?: Schema.Json | undefined;
873
+ } | {
874
+ readonly timerAt: number;
875
+ };
876
+ readonly tag?: Schema.Json | undefined;
877
+ } | {
878
+ readonly type: "runtime.emit";
879
+ readonly event: {
880
+ readonly type: string;
881
+ readonly payload: Schema.Json;
882
+ readonly threadId?: string | null | undefined;
883
+ readonly parentThreadId?: string | null | undefined;
884
+ readonly causationId?: string | null | undefined;
885
+ readonly correlationId?: string | null | undefined;
886
+ readonly effectId?: string | null | undefined;
887
+ readonly origin?: {
888
+ readonly type: "thread";
889
+ readonly threadId: string;
890
+ } | {
891
+ readonly type: "external";
892
+ readonly actorId?: string | undefined;
893
+ } | {
894
+ readonly type: "system";
895
+ } | undefined;
896
+ readonly ephemeral?: boolean | undefined;
897
+ readonly id?: string | undefined;
898
+ readonly ts?: number | undefined;
899
+ readonly idempotencyKey?: string | null | undefined;
900
+ };
901
+ readonly tag?: string | undefined;
902
+ } | {
903
+ readonly type: "runtime.complete";
904
+ readonly output: Schema.Json;
905
+ } | {
906
+ readonly type: "runtime.fail";
907
+ readonly error: string;
908
+ } | {
909
+ readonly type: "runtime.cancel";
910
+ readonly threadId: string;
911
+ readonly tag?: string | undefined;
912
+ } | {
913
+ readonly type: string;
914
+ readonly input: Schema.Json;
915
+ readonly tag?: string | undefined;
916
+ };
917
+ }[];
918
+ readonly effectExecutions: {
919
+ readonly [x: string]: {
920
+ readonly effectId: string;
921
+ readonly attempt: number;
922
+ readonly status: "ambiguous" | "cancel_requested" | "dispatched" | "heartbeat" | "queued" | "retry_wait" | "running" | "started" | "timed_out";
923
+ readonly nextAttemptAt: number | null;
924
+ readonly deadlineAt: number | null;
925
+ readonly startedAt: number | null;
926
+ readonly lastHeartbeatAt: number | null;
927
+ readonly lastError: string | null;
928
+ };
929
+ };
930
+ readonly completedEffectIds: readonly string[];
931
+ readonly processedIdempotencyKeys: readonly string[];
932
+ };
933
+ }>;
934
+ /** Start a new run that continues where a finished run ended. */
935
+ fork: (sourceRunId: string, forkOptions?: {
936
+ runId?: string;
937
+ }) => Promise<{
938
+ readonly runId: string;
939
+ readonly threadId: string;
940
+ readonly state: {
941
+ readonly runId: string;
942
+ readonly status: "cancelled" | "completed" | "failed" | "running";
943
+ readonly rootThreadId: string | null;
944
+ readonly startIdentity: {
945
+ readonly kind: string;
946
+ readonly definitionName: string;
947
+ readonly definitionVersion: string;
948
+ readonly input: Schema.Json;
949
+ readonly requestedThreadId: string | null;
950
+ readonly idempotencyKey: string | null;
951
+ readonly rootThreadId: string;
952
+ } | null;
953
+ readonly forkedFrom: {
954
+ readonly runId: string;
955
+ readonly seq: number;
956
+ } | null;
677
957
  readonly threads: {
678
958
  readonly [x: string]: {
679
959
  readonly threadId: string;
@@ -795,6 +1075,10 @@ export declare function createLoomsClient(options?: LoomsClientOptions): {
795
1075
  readonly idempotencyKey: string | null;
796
1076
  readonly rootThreadId: string;
797
1077
  } | null;
1078
+ readonly forkedFrom: {
1079
+ readonly runId: string;
1080
+ readonly seq: number;
1081
+ } | null;
798
1082
  readonly threads: {
799
1083
  readonly [x: string]: {
800
1084
  readonly threadId: string;
@@ -960,6 +1244,10 @@ export declare function createLoomsClient(options?: LoomsClientOptions): {
960
1244
  readonly idempotencyKey: string | null;
961
1245
  readonly rootThreadId: string;
962
1246
  } | null;
1247
+ readonly forkedFrom: {
1248
+ readonly runId: string;
1249
+ readonly seq: number;
1250
+ } | null;
963
1251
  readonly threads: {
964
1252
  readonly [x: string]: {
965
1253
  readonly threadId: string;
@@ -1078,6 +1366,10 @@ export declare function createLoomsClient(options?: LoomsClientOptions): {
1078
1366
  readonly idempotencyKey: string | null;
1079
1367
  readonly rootThreadId: string;
1080
1368
  } | null;
1369
+ readonly forkedFrom: {
1370
+ readonly runId: string;
1371
+ readonly seq: number;
1372
+ } | null;
1081
1373
  readonly threads: {
1082
1374
  readonly [x: string]: {
1083
1375
  readonly threadId: string;
@@ -14,6 +14,12 @@ const StartResultSchema = Schema.Struct({
14
14
  state: RunStateSchema,
15
15
  });
16
16
  const RunResultSchema = Schema.Struct({ runId: Schema.String, state: RunStateSchema });
17
+ /** `appended` counts the events the run gained while the wake ran. */
18
+ const WakeResultSchema = Schema.Struct({
19
+ runId: Schema.String,
20
+ state: RunStateSchema,
21
+ appended: Schema.Finite,
22
+ });
17
23
  const EventsResultSchema = Schema.Struct({
18
24
  runId: Schema.String,
19
25
  events: Schema.Array(EventEnvelopeSchema),
@@ -157,7 +163,17 @@ export function createLoomsClient(options = {}) {
157
163
  method: 'POST',
158
164
  body: stringifyJson({ events }),
159
165
  }),
160
- wake: (runId) => request(`/runs/${encodeURIComponent(runId)}/wake`, RunResultSchema, { method: 'POST' }),
166
+ wake: (runId) => request(`/runs/${encodeURIComponent(runId)}/wake`, WakeResultSchema, { method: 'POST' }),
167
+ /** Cancel a thread and its descendants; omit `threadId` to cancel the whole run. */
168
+ cancel: (runId, threadId) => request(`/runs/${encodeURIComponent(runId)}/cancel`, RunResultSchema, {
169
+ method: 'POST',
170
+ body: stringifyJson(threadId === undefined ? {} : { threadId }),
171
+ }),
172
+ /** Start a new run that continues where a finished run ended. */
173
+ fork: (sourceRunId, forkOptions) => request(`/runs/${encodeURIComponent(sourceRunId)}/fork`, StartResultSchema, {
174
+ method: 'POST',
175
+ body: stringifyJson(forkOptions?.runId === undefined ? {} : { runId: forkOptions.runId }),
176
+ }),
161
177
  workerCallback,
162
178
  listRunSummaries: () => request('/runs/summaries', RunSummariesSchema),
163
179
  listDefinitions: () => request('/definitions', DefinitionCatalogSchema),
@@ -6,7 +6,7 @@ export interface DurableObjectNamespaceLike<T extends DurableObjectStubLike = Du
6
6
  }
7
7
  /**
8
8
  * Routes incoming HTTP requests to the appropriate Durable Object actor cell.
9
- * Handles `/health` locally, returns 501 for global run lists, and forwards
9
+ * Handles `/health` locally, returns 501 for global run lists and forks, and forwards
10
10
  * all other run and event-stream requests to `namespace.getByName(runId)`.
11
11
  */
12
12
  export declare function routeToDurableObject(namespace: DurableObjectNamespaceLike, req: Request): Promise<Response | null>;
@@ -1,18 +1,16 @@
1
- import { resolveRunTarget, RunTargetError } from '@swirls/looms/actor';
1
+ import { crossRunResponse, resolveRunTarget, RunTargetError } from '@swirls/looms/actor';
2
2
  /**
3
3
  * Routes incoming HTTP requests to the appropriate Durable Object actor cell.
4
- * Handles `/health` locally, returns 501 for global run lists, and forwards
4
+ * Handles `/health` locally, returns 501 for global run lists and forks, and forwards
5
5
  * all other run and event-stream requests to `namespace.getByName(runId)`.
6
6
  */
7
7
  export function routeToDurableObject(namespace, req) {
8
- const url = new URL(req.url);
9
- if (url.pathname === '/health') {
8
+ if (new URL(req.url).pathname === '/health') {
10
9
  return Promise.resolve(Response.json({ ok: true }));
11
10
  }
12
- if (req.method === 'GET' && (url.pathname === '/runs' || url.pathname === '/runs/summaries')) {
13
- return Promise.resolve(Response.json({
14
- error: 'Global run listing is not supported in the Durable Object backend without a global index',
15
- }, { status: 501 }));
11
+ const unsupported = crossRunResponse(req, 'the Durable Object backend');
12
+ if (unsupported) {
13
+ return Promise.resolve(unsupported);
16
14
  }
17
15
  return resolveRunTarget(req)
18
16
  .then(({ runId, request }) => namespace.getByName(runId).fetch(request))
@@ -0,0 +1,8 @@
1
+ import type { AnyEventEnvelope } from './register';
2
+ /** The effect a `runtime.effect.retry.scheduled` event retries, or `undefined` for other events. */
3
+ export declare function retriedEffectId(event: AnyEventEnvelope): string | undefined;
4
+ /**
5
+ * Drops ephemeral events from effect attempts that a later retry in `events` superseded. Durable
6
+ * events and ephemeral events from the current attempt are kept.
7
+ */
8
+ export declare function dropSupersededAttempts<E extends AnyEventEnvelope>(events: readonly E[]): readonly E[];
@@ -0,0 +1,27 @@
1
+ import { Schema } from 'effect';
2
+ import { protocolCatalog } from './protocol.js';
3
+ const RETRY_SCHEDULED = 'runtime.effect.retry.scheduled';
4
+ const isRetryPayload = Schema.is(protocolCatalog.entries['effect.retry.scheduled']);
5
+ /** The effect a `runtime.effect.retry.scheduled` event retries, or `undefined` for other events. */
6
+ export function retriedEffectId(event) {
7
+ return event.type === RETRY_SCHEDULED && isRetryPayload(event.payload)
8
+ ? event.payload.effectId
9
+ : undefined;
10
+ }
11
+ /**
12
+ * Drops ephemeral events from effect attempts that a later retry in `events` superseded. Durable
13
+ * events and ephemeral events from the current attempt are kept.
14
+ */
15
+ export function dropSupersededAttempts(events) {
16
+ const lastRetry = new Map();
17
+ events.forEach((event, index) => {
18
+ const effectId = retriedEffectId(event);
19
+ if (effectId !== undefined) {
20
+ lastRetry.set(effectId, index);
21
+ }
22
+ });
23
+ if (lastRetry.size === 0) {
24
+ return events;
25
+ }
26
+ return events.filter((event, index) => !event.ephemeral || !event.effectId || (lastRetry.get(event.effectId) ?? -1) < index);
27
+ }
@@ -166,6 +166,11 @@ export interface EffectDefinition<TInput = JsonValue, R = Context.Service.Any, E
166
166
  readonly execution?: EffectExecution;
167
167
  /** Whether `execute` runs a user handler. Without one, the effect can only complete through a worker. */
168
168
  readonly hasHandler: boolean;
169
+ /**
170
+ * The outcome follows from the input alone, like scheduling from state already folded. Forking
171
+ * a failed run retries the effect that produced the failure, not a deterministic one passing it on.
172
+ */
173
+ readonly deterministic?: boolean;
169
174
  readonly [effectInputType]?: (input: TInput) => TInput;
170
175
  execute(input: JsonValue, ctx: EffectContext): Effect.Effect<ReadonlyArray<E>, InvalidInputError | EffectHandlerError, R>;
171
176
  }
@@ -182,6 +187,7 @@ export declare function defineEffect<TSchema extends SchemaInput, R = never, E e
182
187
  input: TSchema;
183
188
  retry?: RetryPolicy;
184
189
  execution?: EffectExecution;
190
+ deterministic?: boolean;
185
191
  execute?: (input: InferDefinedSchema<TSchema>, ctx: EffectContext) => EffectHandlerResult<R, E, HandlerError>;
186
192
  }): EffectDefinition<InferDefinedSchema<TSchema>, R, E>;
187
193
  export declare function defineEffect<R = never, E extends EventInput = EventInput, HandlerError = never>(def: {
@@ -189,6 +195,7 @@ export declare function defineEffect<R = never, E extends EventInput = EventInpu
189
195
  input?: undefined;
190
196
  retry?: RetryPolicy;
191
197
  execution?: EffectExecution;
198
+ deterministic?: boolean;
192
199
  execute?: (input: JsonValue, ctx: EffectContext) => EffectHandlerResult<R, E, HandlerError>;
193
200
  }): EffectDefinition<JsonValue, R, E>;
194
201
  export declare function spawn(args: {
@@ -106,6 +106,7 @@ export function defineEffect(def) {
106
106
  retry: def.retry,
107
107
  execution: def.execution,
108
108
  hasHandler: handler !== undefined,
109
+ deterministic: def.deterministic,
109
110
  execute: (raw, ctx) => Effect.gen(function* () {
110
111
  const input = schema ? yield* validateInputEffect(schema, raw) : raw;
111
112
  return handler ? yield* liftHandlerResult(handler(input, ctx)) : noOutput;
package/dist/core/fold.js CHANGED
@@ -2,7 +2,7 @@ import { Option, Predicate, Schema, Stream } from 'effect';
2
2
  import { isWithdrawnError, WaitConditionSchema } from './effects.js';
3
3
  import { createEffectId } from './ids.js';
4
4
  import { DEFAULT_DEFINITION_VERSION } from './module.js';
5
- import { emptyRunState } from './state.js';
5
+ import { completedRunStatus, emptyRunState } from './state.js';
6
6
  import { isTerminalStatus } from './thread.js';
7
7
  const decodeWaitCondition = Schema.decodeUnknownOption(WaitConditionSchema);
8
8
  function payloadObject(event) {
@@ -25,6 +25,7 @@ function cloneState(state) {
25
25
  status: state.status,
26
26
  rootThreadId: state.rootThreadId,
27
27
  startIdentity: state.startIdentity,
28
+ forkedFrom: state.forkedFrom,
28
29
  threads: { ...state.threads },
29
30
  waits: { ...state.waits },
30
31
  outstandingEffects: [...state.outstandingEffects],
@@ -104,13 +105,18 @@ function applyProtocol(state, event, registry) {
104
105
  status: 'running',
105
106
  };
106
107
  }
107
- case 'runtime.run.completed': {
108
- const error = payload.error;
109
- const status = Predicate.isString(error) && error.length > 0 ? 'failed' : 'completed';
110
- return { ...state, status };
111
- }
108
+ case 'runtime.run.completed':
109
+ return { ...state, status: completedRunStatus(payload) };
112
110
  case 'runtime.run.cancelled':
113
111
  return { ...state, status: 'cancelled' };
112
+ case 'runtime.run.forked': {
113
+ const sourceRunId = readString(payload, 'sourceRunId');
114
+ const sourceSeq = payload.sourceSeq;
115
+ // Attempts recorded in the copied history belong to the source run's processes.
116
+ return sourceRunId && Predicate.isNumber(sourceSeq)
117
+ ? { ...state, forkedFrom: { runId: sourceRunId, seq: sourceSeq }, effectExecutions: {} }
118
+ : state;
119
+ }
114
120
  case 'runtime.thread.started': {
115
121
  const threadId = readString(payload, 'threadId') ?? event.threadId;
116
122
  const kind = readString(payload, 'kind');
@@ -0,0 +1,35 @@
1
+ import { Effect } from 'effect';
2
+ import { type EffectDefinition } from './effects';
3
+ import { type EventEnvelope } from './envelope';
4
+ import { type FoldRegistry } from './fold';
5
+ declare const ForkSourceError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
6
+ readonly _tag: "ForkSourceError";
7
+ } & Readonly<A>;
8
+ export declare class ForkSourceError extends ForkSourceError_base<{
9
+ readonly runId: string;
10
+ readonly message: string;
11
+ }> {
12
+ constructor(runId: string, message: string);
13
+ }
14
+ export interface ForkRegistry extends FoldRegistry {
15
+ readonly effects: ReadonlyMap<string, Pick<EffectDefinition, 'deterministic'>>;
16
+ }
17
+ export interface ForkPlan {
18
+ /** The fork's history: the copied prefix, keeping source seqs, then the `runtime.run.forked` marker. */
19
+ readonly events: readonly EventEnvelope[];
20
+ /** Last source seq the fork copied. */
21
+ readonly sourceSeq: number;
22
+ readonly rootThreadId: string;
23
+ }
24
+ /**
25
+ * Plan a new run that continues where the source run ended. The fork copies the source's
26
+ * history up to the point the run finished, keeping sequence numbers so effect ids still
27
+ * line up, and its next wake re-issues whatever effects were outstanding there.
28
+ */
29
+ export declare function planFork(input: {
30
+ readonly sourceRunId: string;
31
+ readonly runId: string;
32
+ readonly events: readonly EventEnvelope[];
33
+ readonly registry: ForkRegistry;
34
+ }): Effect.Effect<ForkPlan, ForkSourceError>;
35
+ export {};