@smthrs/notifications 0.0.0-stage → 1.0.0-rc.13

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 (146) hide show
  1. package/CHANGELOG.md +127 -0
  2. package/LICENSE +21 -0
  3. package/README.md +93 -2
  4. package/dist/cjs/AlertPolicy.d.ts +284 -0
  5. package/dist/cjs/AlertPolicy.d.ts.map +1 -0
  6. package/dist/cjs/AlertPolicy.js +124 -0
  7. package/dist/cjs/AlertPolicy.js.map +7 -0
  8. package/dist/cjs/AlertRuntime.d.ts +90 -0
  9. package/dist/cjs/AlertRuntime.d.ts.map +1 -0
  10. package/dist/cjs/AlertRuntime.js +166 -0
  11. package/dist/cjs/AlertRuntime.js.map +7 -0
  12. package/dist/cjs/AlertSink.d.ts +101 -0
  13. package/dist/cjs/AlertSink.d.ts.map +1 -0
  14. package/dist/cjs/AlertSink.js +44 -0
  15. package/dist/cjs/AlertSink.js.map +7 -0
  16. package/dist/cjs/Alerts.d.ts +57 -0
  17. package/dist/cjs/Alerts.d.ts.map +1 -0
  18. package/dist/cjs/Alerts.js +46 -0
  19. package/dist/cjs/Alerts.js.map +7 -0
  20. package/dist/cjs/Notification.d.ts +214 -0
  21. package/dist/cjs/Notification.d.ts.map +1 -0
  22. package/dist/cjs/Notification.js +62 -0
  23. package/dist/cjs/Notification.js.map +7 -0
  24. package/dist/cjs/NotificationEvent.d.ts +214 -0
  25. package/dist/cjs/NotificationEvent.d.ts.map +1 -0
  26. package/dist/cjs/NotificationEvent.js +84 -0
  27. package/dist/cjs/NotificationEvent.js.map +7 -0
  28. package/dist/cjs/NotificationQueue.d.ts +194 -0
  29. package/dist/cjs/NotificationQueue.d.ts.map +1 -0
  30. package/dist/cjs/NotificationQueue.js +477 -0
  31. package/dist/cjs/NotificationQueue.js.map +7 -0
  32. package/dist/cjs/NotificationState.d.ts +169 -0
  33. package/dist/cjs/NotificationState.d.ts.map +1 -0
  34. package/dist/cjs/NotificationState.js +110 -0
  35. package/dist/cjs/NotificationState.js.map +7 -0
  36. package/dist/cjs/Projection.d.ts +30 -0
  37. package/dist/cjs/Projection.d.ts.map +1 -0
  38. package/dist/cjs/Projection.js +53 -0
  39. package/dist/cjs/Projection.js.map +7 -0
  40. package/dist/cjs/SteerPayload.d.ts +177 -0
  41. package/dist/cjs/SteerPayload.d.ts.map +1 -0
  42. package/dist/cjs/SteerPayload.js +61 -0
  43. package/dist/cjs/SteerPayload.js.map +7 -0
  44. package/dist/cjs/index.d.ts +41 -0
  45. package/dist/cjs/index.d.ts.map +1 -0
  46. package/dist/cjs/index.js +47 -0
  47. package/dist/cjs/index.js.map +7 -0
  48. package/dist/cjs/internal/foldCache.d.ts +36 -0
  49. package/dist/cjs/internal/foldCache.d.ts.map +1 -0
  50. package/dist/cjs/internal/foldCache.js +54 -0
  51. package/dist/cjs/internal/foldCache.js.map +7 -0
  52. package/dist/cjs/internal/replay.d.ts +22 -0
  53. package/dist/cjs/internal/replay.d.ts.map +1 -0
  54. package/dist/cjs/internal/replay.js +46 -0
  55. package/dist/cjs/internal/replay.js.map +7 -0
  56. package/dist/cjs/layerWebhook.d.ts +60 -0
  57. package/dist/cjs/layerWebhook.d.ts.map +1 -0
  58. package/dist/cjs/layerWebhook.js +145 -0
  59. package/dist/cjs/layerWebhook.js.map +7 -0
  60. package/dist/cjs/package.json +1 -0
  61. package/dist/esm/AlertPolicy.d.ts +284 -0
  62. package/dist/esm/AlertPolicy.d.ts.map +1 -0
  63. package/dist/esm/AlertPolicy.js +284 -0
  64. package/dist/esm/AlertPolicy.js.map +1 -0
  65. package/dist/esm/AlertRuntime.d.ts +90 -0
  66. package/dist/esm/AlertRuntime.d.ts.map +1 -0
  67. package/dist/esm/AlertRuntime.js +191 -0
  68. package/dist/esm/AlertRuntime.js.map +1 -0
  69. package/dist/esm/AlertSink.d.ts +101 -0
  70. package/dist/esm/AlertSink.d.ts.map +1 -0
  71. package/dist/esm/AlertSink.js +63 -0
  72. package/dist/esm/AlertSink.js.map +1 -0
  73. package/dist/esm/Alerts.d.ts +57 -0
  74. package/dist/esm/Alerts.d.ts.map +1 -0
  75. package/dist/esm/Alerts.js +57 -0
  76. package/dist/esm/Alerts.js.map +1 -0
  77. package/dist/esm/Notification.d.ts +214 -0
  78. package/dist/esm/Notification.d.ts.map +1 -0
  79. package/dist/esm/Notification.js +121 -0
  80. package/dist/esm/Notification.js.map +1 -0
  81. package/dist/esm/NotificationEvent.d.ts +214 -0
  82. package/dist/esm/NotificationEvent.d.ts.map +1 -0
  83. package/dist/esm/NotificationEvent.js +158 -0
  84. package/dist/esm/NotificationEvent.js.map +1 -0
  85. package/dist/esm/NotificationQueue.d.ts +194 -0
  86. package/dist/esm/NotificationQueue.d.ts.map +1 -0
  87. package/dist/esm/NotificationQueue.js +625 -0
  88. package/dist/esm/NotificationQueue.js.map +1 -0
  89. package/dist/esm/NotificationState.d.ts +169 -0
  90. package/dist/esm/NotificationState.d.ts.map +1 -0
  91. package/dist/esm/NotificationState.js +191 -0
  92. package/dist/esm/NotificationState.js.map +1 -0
  93. package/dist/esm/Projection.d.ts +30 -0
  94. package/dist/esm/Projection.d.ts.map +1 -0
  95. package/dist/esm/Projection.js +47 -0
  96. package/dist/esm/Projection.js.map +1 -0
  97. package/dist/esm/SteerPayload.d.ts +177 -0
  98. package/dist/esm/SteerPayload.d.ts.map +1 -0
  99. package/dist/esm/SteerPayload.js +139 -0
  100. package/dist/esm/SteerPayload.js.map +1 -0
  101. package/dist/esm/index.d.ts +41 -0
  102. package/dist/esm/index.d.ts.map +1 -0
  103. package/dist/esm/index.js +41 -0
  104. package/dist/esm/index.js.map +1 -0
  105. package/dist/esm/internal/foldCache.d.ts +36 -0
  106. package/dist/esm/internal/foldCache.d.ts.map +1 -0
  107. package/dist/esm/internal/foldCache.js +58 -0
  108. package/dist/esm/internal/foldCache.js.map +1 -0
  109. package/dist/esm/internal/replay.d.ts +22 -0
  110. package/dist/esm/internal/replay.d.ts.map +1 -0
  111. package/dist/esm/internal/replay.js +31 -0
  112. package/dist/esm/internal/replay.js.map +1 -0
  113. package/dist/esm/layerWebhook.d.ts +60 -0
  114. package/dist/esm/layerWebhook.d.ts.map +1 -0
  115. package/dist/esm/layerWebhook.js +186 -0
  116. package/dist/esm/layerWebhook.js.map +1 -0
  117. package/docs/README.md +146 -0
  118. package/docs/api.md +443 -0
  119. package/docs/concepts/admission-and-promotion.md +144 -0
  120. package/docs/concepts/alerting.md +138 -0
  121. package/docs/concepts/journal-records.md +132 -0
  122. package/docs/guides/alert-on-a-stuck-run.md +140 -0
  123. package/docs/guides/drain-at-a-turn-boundary.md +95 -0
  124. package/docs/guides/handle-a-full-queue.md +102 -0
  125. package/docs/guides/report-pending-notifications.md +92 -0
  126. package/docs/guides/send-alerts-to-a-webhook.md +155 -0
  127. package/docs/guides/steer-a-run.md +127 -0
  128. package/docs/guides/testing.md +161 -0
  129. package/docs/installation.md +123 -0
  130. package/docs/quickstart.md +159 -0
  131. package/docs/troubleshooting.md +254 -0
  132. package/package.json +182 -3
  133. package/src/AlertPolicy.ts +377 -0
  134. package/src/AlertRuntime.ts +280 -0
  135. package/src/AlertSink.ts +105 -0
  136. package/src/Alerts.ts +71 -0
  137. package/src/Notification.ts +171 -0
  138. package/src/NotificationEvent.ts +213 -0
  139. package/src/NotificationQueue.ts +856 -0
  140. package/src/NotificationState.ts +295 -0
  141. package/src/Projection.ts +48 -0
  142. package/src/SteerPayload.ts +198 -0
  143. package/src/index.ts +47 -0
  144. package/src/internal/foldCache.ts +72 -0
  145. package/src/internal/replay.ts +39 -0
  146. package/src/layerWebhook.ts +239 -0
@@ -0,0 +1,177 @@
1
+ /**
2
+ * The steering vocabulary carried inside a notification payload.
3
+ *
4
+ * A steer crosses two packages that must not depend on each other. A control
5
+ * plane admits it into the durable queue; a harness drains it at a turn
6
+ * boundary and turns it into a change the model sees. Both depend on this
7
+ * package, so the shape they have to agree on lives here rather than in
8
+ * either of them.
9
+ *
10
+ * The payload is stored as JSON in the journal, which makes decoding the
11
+ * interesting half. Only a `human-steer` or `human-followup` carries a steer: a
12
+ * `system-event` decodes as nothing whatever its payload says, because system
13
+ * events carry webhook bodies and machine reports, and reading one as an
14
+ * instruction would let its producer message the model or change its seat,
15
+ * thinking, or tools. Inside a human notification, a record carrying a `body`
16
+ * string and no `kind` decodes as a message, because that is what a minimal
17
+ * caller means by it: the control plane's steer RPC accepts the same shape. A
18
+ * payload this module cannot classify decodes as nothing at all.
19
+ *
20
+ * @since 0.1.0
21
+ */
22
+ import { Schema } from "effect";
23
+ import type * as Notification from "./Notification.ts";
24
+ /**
25
+ * How hard the model should think.
26
+ *
27
+ * A subset of `ModelRequest.ReasoningEffort` in `@smthrs/model`, which this
28
+ * package deliberately does not depend on: a durable payload schema that
29
+ * pulled in a provider client would make every queue reader carry one. Every
30
+ * literal here is one of that schema's, so the harness assigns a `Thinking`
31
+ * to a `ReasoningEffort` directly; `ReasoningEffort` also admits `"max"`,
32
+ * which steering does not offer.
33
+ *
34
+ * @category models
35
+ * @since 0.1.0
36
+ */
37
+ export declare const Thinking: Schema.Literals<readonly ["none", "minimal", "low", "medium", "high", "xhigh"]>;
38
+ /**
39
+ * How hard the model should think.
40
+ *
41
+ * @category models
42
+ * @since 0.1.0
43
+ */
44
+ export type Thinking = typeof Thinking.Type;
45
+ /**
46
+ * A message inserted into the transcript at the next turn boundary.
47
+ *
48
+ * The body may be empty. An operator who steers an empty message has still told
49
+ * the run something: that a human is watching, and that the turn should
50
+ * continue. Refusing it here would turn a deliberate nudge into a lost steer.
51
+ *
52
+ * @category models
53
+ * @since 0.1.0
54
+ */
55
+ export declare const MessagePayload: Schema.Struct<{
56
+ readonly kind: Schema.Literal<"Message">;
57
+ readonly body: Schema.String;
58
+ }>;
59
+ /**
60
+ * The decoded form of {@link MessagePayload}.
61
+ *
62
+ * @category models
63
+ * @since 0.1.0
64
+ */
65
+ export type MessagePayload = typeof MessagePayload.Type;
66
+ /**
67
+ * A model-seat change that applies from the next turn on.
68
+ *
69
+ * @category models
70
+ * @since 0.1.0
71
+ */
72
+ export declare const SeatPayload: Schema.Struct<{
73
+ readonly kind: Schema.Literal<"Seat">;
74
+ readonly seat: Schema.NonEmptyString;
75
+ }>;
76
+ /**
77
+ * The decoded form of {@link SeatPayload}.
78
+ *
79
+ * @category models
80
+ * @since 0.1.0
81
+ */
82
+ export type SeatPayload = typeof SeatPayload.Type;
83
+ /**
84
+ * A thinking-level change that applies from the next turn on.
85
+ *
86
+ * @category models
87
+ * @since 0.1.0
88
+ */
89
+ export declare const ThinkingPayload: Schema.Struct<{
90
+ readonly kind: Schema.Literal<"Thinking">;
91
+ readonly thinking: Schema.Literals<readonly ["none", "minimal", "low", "medium", "high", "xhigh"]>;
92
+ }>;
93
+ /**
94
+ * The decoded form of {@link ThinkingPayload}.
95
+ *
96
+ * @category models
97
+ * @since 0.1.0
98
+ */
99
+ export type ThinkingPayload = typeof ThinkingPayload.Type;
100
+ /**
101
+ * Tools added to the active set for future turns.
102
+ *
103
+ * Additive only: steering can widen what the agent may reach for, and cannot
104
+ * narrow it, because a turn already in flight may hold a call to a tool a
105
+ * narrowing steer would have removed.
106
+ *
107
+ * @category models
108
+ * @since 0.1.0
109
+ */
110
+ export declare const ToolsPayload: Schema.Struct<{
111
+ readonly kind: Schema.Literal<"Tools">;
112
+ readonly toolNames: Schema.NonEmptyArray<Schema.NonEmptyString>;
113
+ }>;
114
+ /**
115
+ * The decoded form of {@link ToolsPayload}.
116
+ *
117
+ * @category models
118
+ * @since 0.1.0
119
+ */
120
+ export type ToolsPayload = typeof ToolsPayload.Type;
121
+ /**
122
+ * Any steering item a notification payload can carry.
123
+ *
124
+ * @category models
125
+ * @since 0.1.0
126
+ */
127
+ export declare const SteerPayload: Schema.Union<readonly [Schema.Struct<{
128
+ readonly kind: Schema.Literal<"Message">;
129
+ readonly body: Schema.String;
130
+ }>, Schema.Struct<{
131
+ readonly kind: Schema.Literal<"Seat">;
132
+ readonly seat: Schema.NonEmptyString;
133
+ }>, Schema.Struct<{
134
+ readonly kind: Schema.Literal<"Thinking">;
135
+ readonly thinking: Schema.Literals<readonly ["none", "minimal", "low", "medium", "high", "xhigh"]>;
136
+ }>, Schema.Struct<{
137
+ readonly kind: Schema.Literal<"Tools">;
138
+ readonly toolNames: Schema.NonEmptyArray<Schema.NonEmptyString>;
139
+ }>]>;
140
+ /**
141
+ * Any steering item a notification payload can carry.
142
+ *
143
+ * @category models
144
+ * @since 0.1.0
145
+ */
146
+ export type SteerPayload = typeof SteerPayload.Type;
147
+ /**
148
+ * Reads a notification as a steering item, or reports that it is not one.
149
+ *
150
+ * The notification, not its payload, is the argument: a `system-event` is
151
+ * never a steer, so the classification cannot be separated from who sent it.
152
+ *
153
+ * @param notification the admitted notification
154
+ * @category conversions
155
+ * @since 0.1.0
156
+ */
157
+ export declare const decode: (notification: Notification.Notification) => SteerPayload | undefined;
158
+ /**
159
+ * The payload a steering item is stored as.
160
+ *
161
+ * Every item is written with its `kind`, including a message: the body-only
162
+ * form is read as a convenience and never written.
163
+ *
164
+ * The result is typed as the item itself. Every item is JSON, so it is
165
+ * assignable to `Notification.payload` without an assertion.
166
+ *
167
+ * The returned record shares no mutable structure with `item`. A caller hands
168
+ * the result to an admission that serializes it later, so an array still
169
+ * aliased to the caller could change what is durably journaled after the call
170
+ * returned.
171
+ *
172
+ * @param item the steering item
173
+ * @category conversions
174
+ * @since 0.1.0
175
+ */
176
+ export declare const encode: (item: SteerPayload) => SteerPayload;
177
+ //# sourceMappingURL=SteerPayload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SteerPayload.d.ts","sourceRoot":"","sources":["../../src/SteerPayload.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/B,OAAO,KAAK,KAAK,YAAY,MAAM,mBAAmB,CAAA;AAEtD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,QAAQ,iFAAyE,CAAA;AAE9F;;;;;GAKG;AACH,MAAM,MAAM,QAAQ,GAAG,OAAO,QAAQ,CAAC,IAAI,CAAA;AAE3C;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc;;;EAGzB,CAAA;AAEF;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAA;AAEvD;;;;;GAKG;AACH,eAAO,MAAM,WAAW;;;EAGtB,CAAA;AAEF;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,OAAO,WAAW,CAAC,IAAI,CAAA;AAEjD;;;;;GAKG;AACH,eAAO,MAAM,eAAe;;;EAG1B,CAAA;AAEF;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,OAAO,eAAe,CAAC,IAAI,CAAA;AAEzD;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY;;;EAGvB,CAAA;AAEF;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAC,IAAI,CAAA;AAEnD;;;;;GAKG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;IAA6E,CAAA;AAEtG;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAC,IAAI,CAAA;AASnD;;;;;;;;;GASG;AACH,eAAO,MAAM,MAAM,GAAI,cAAc,YAAY,CAAC,YAAY,KAAG,YAAY,GAAG,SAW/E,CAAA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,MAAM,GAAI,MAAM,YAAY,KAAG,YACuC,CAAA"}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * The steering vocabulary carried inside a notification payload.
3
+ *
4
+ * A steer crosses two packages that must not depend on each other. A control
5
+ * plane admits it into the durable queue; a harness drains it at a turn
6
+ * boundary and turns it into a change the model sees. Both depend on this
7
+ * package, so the shape they have to agree on lives here rather than in
8
+ * either of them.
9
+ *
10
+ * The payload is stored as JSON in the journal, which makes decoding the
11
+ * interesting half. Only a `human-steer` or `human-followup` carries a steer: a
12
+ * `system-event` decodes as nothing whatever its payload says, because system
13
+ * events carry webhook bodies and machine reports, and reading one as an
14
+ * instruction would let its producer message the model or change its seat,
15
+ * thinking, or tools. Inside a human notification, a record carrying a `body`
16
+ * string and no `kind` decodes as a message, because that is what a minimal
17
+ * caller means by it: the control plane's steer RPC accepts the same shape. A
18
+ * payload this module cannot classify decodes as nothing at all.
19
+ *
20
+ * @since 0.1.0
21
+ */
22
+ import { Schema } from "effect";
23
+ /**
24
+ * How hard the model should think.
25
+ *
26
+ * A subset of `ModelRequest.ReasoningEffort` in `@smthrs/model`, which this
27
+ * package deliberately does not depend on: a durable payload schema that
28
+ * pulled in a provider client would make every queue reader carry one. Every
29
+ * literal here is one of that schema's, so the harness assigns a `Thinking`
30
+ * to a `ReasoningEffort` directly; `ReasoningEffort` also admits `"max"`,
31
+ * which steering does not offer.
32
+ *
33
+ * @category models
34
+ * @since 0.1.0
35
+ */
36
+ export const Thinking = Schema.Literals(["none", "minimal", "low", "medium", "high", "xhigh"]);
37
+ /**
38
+ * A message inserted into the transcript at the next turn boundary.
39
+ *
40
+ * The body may be empty. An operator who steers an empty message has still told
41
+ * the run something: that a human is watching, and that the turn should
42
+ * continue. Refusing it here would turn a deliberate nudge into a lost steer.
43
+ *
44
+ * @category models
45
+ * @since 0.1.0
46
+ */
47
+ export const MessagePayload = Schema.Struct({
48
+ kind: Schema.Literal("Message"),
49
+ body: Schema.String
50
+ });
51
+ /**
52
+ * A model-seat change that applies from the next turn on.
53
+ *
54
+ * @category models
55
+ * @since 0.1.0
56
+ */
57
+ export const SeatPayload = Schema.Struct({
58
+ kind: Schema.Literal("Seat"),
59
+ seat: Schema.NonEmptyString
60
+ });
61
+ /**
62
+ * A thinking-level change that applies from the next turn on.
63
+ *
64
+ * @category models
65
+ * @since 0.1.0
66
+ */
67
+ export const ThinkingPayload = Schema.Struct({
68
+ kind: Schema.Literal("Thinking"),
69
+ thinking: Thinking
70
+ });
71
+ /**
72
+ * Tools added to the active set for future turns.
73
+ *
74
+ * Additive only: steering can widen what the agent may reach for, and cannot
75
+ * narrow it, because a turn already in flight may hold a call to a tool a
76
+ * narrowing steer would have removed.
77
+ *
78
+ * @category models
79
+ * @since 0.1.0
80
+ */
81
+ export const ToolsPayload = Schema.Struct({
82
+ kind: Schema.Literal("Tools"),
83
+ toolNames: Schema.NonEmptyArray(Schema.NonEmptyString)
84
+ });
85
+ /**
86
+ * Any steering item a notification payload can carry.
87
+ *
88
+ * @category models
89
+ * @since 0.1.0
90
+ */
91
+ export const SteerPayload = Schema.Union([MessagePayload, SeatPayload, ThinkingPayload, ToolsPayload]);
92
+ const parse = Schema.decodeUnknownOption(SteerPayload);
93
+ const record = (value) => typeof value === "object" && value !== null && !Array.isArray(value)
94
+ ? value
95
+ : undefined;
96
+ /**
97
+ * Reads a notification as a steering item, or reports that it is not one.
98
+ *
99
+ * The notification, not its payload, is the argument: a `system-event` is
100
+ * never a steer, so the classification cannot be separated from who sent it.
101
+ *
102
+ * @param notification the admitted notification
103
+ * @category conversions
104
+ * @since 0.1.0
105
+ */
106
+ export const decode = (notification) => {
107
+ if (notification._tag === "system-event")
108
+ return undefined;
109
+ const fields = record(notification.payload);
110
+ if (fields === undefined)
111
+ return undefined;
112
+ // A record with a body and no kind is a message: it is the shape a caller
113
+ // writes when it has nothing else to say.
114
+ const candidate = fields["kind"] === undefined && typeof fields["body"] === "string"
115
+ ? { kind: "Message", body: fields["body"] }
116
+ : fields;
117
+ const decoded = parse(candidate);
118
+ return decoded._tag === "Some" ? decoded.value : undefined;
119
+ };
120
+ /**
121
+ * The payload a steering item is stored as.
122
+ *
123
+ * Every item is written with its `kind`, including a message: the body-only
124
+ * form is read as a convenience and never written.
125
+ *
126
+ * The result is typed as the item itself. Every item is JSON, so it is
127
+ * assignable to `Notification.payload` without an assertion.
128
+ *
129
+ * The returned record shares no mutable structure with `item`. A caller hands
130
+ * the result to an admission that serializes it later, so an array still
131
+ * aliased to the caller could change what is durably journaled after the call
132
+ * returned.
133
+ *
134
+ * @param item the steering item
135
+ * @category conversions
136
+ * @since 0.1.0
137
+ */
138
+ export const encode = (item) => item.kind === "Tools" ? { ...item, toolNames: [...item.toolNames] } : { ...item };
139
+ //# sourceMappingURL=SteerPayload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SteerPayload.js","sourceRoot":"","sources":["../../src/SteerPayload.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The steering vocabulary carried inside a notification payload.\n *\n * A steer crosses two packages that must not depend on each other. A control\n * plane admits it into the durable queue; a harness drains it at a turn\n * boundary and turns it into a change the model sees. Both depend on this\n * package, so the shape they have to agree on lives here rather than in\n * either of them.\n *\n * The payload is stored as JSON in the journal, which makes decoding the\n * interesting half. Only a `human-steer` or `human-followup` carries a steer: a\n * `system-event` decodes as nothing whatever its payload says, because system\n * events carry webhook bodies and machine reports, and reading one as an\n * instruction would let its producer message the model or change its seat,\n * thinking, or tools. Inside a human notification, a record carrying a `body`\n * string and no `kind` decodes as a message, because that is what a minimal\n * caller means by it: the control plane's steer RPC accepts the same shape. A\n * payload this module cannot classify decodes as nothing at all.\n *\n * @since 0.1.0\n */\n\nimport { Schema } from \"effect\"\nimport type * as Notification from \"./Notification.ts\"\n\n/**\n * How hard the model should think.\n *\n * A subset of `ModelRequest.ReasoningEffort` in `@smthrs/model`, which this\n * package deliberately does not depend on: a durable payload schema that\n * pulled in a provider client would make every queue reader carry one. Every\n * literal here is one of that schema's, so the harness assigns a `Thinking`\n * to a `ReasoningEffort` directly; `ReasoningEffort` also admits `\"max\"`,\n * which steering does not offer.\n *\n * @category models\n * @since 0.1.0\n */\nexport const Thinking = Schema.Literals([\"none\", \"minimal\", \"low\", \"medium\", \"high\", \"xhigh\"])\n\n/**\n * How hard the model should think.\n *\n * @category models\n * @since 0.1.0\n */\nexport type Thinking = typeof Thinking.Type\n\n/**\n * A message inserted into the transcript at the next turn boundary.\n *\n * The body may be empty. An operator who steers an empty message has still told\n * the run something: that a human is watching, and that the turn should\n * continue. Refusing it here would turn a deliberate nudge into a lost steer.\n *\n * @category models\n * @since 0.1.0\n */\nexport const MessagePayload = Schema.Struct({\n kind: Schema.Literal(\"Message\"),\n body: Schema.String\n})\n\n/**\n * The decoded form of {@link MessagePayload}.\n *\n * @category models\n * @since 0.1.0\n */\nexport type MessagePayload = typeof MessagePayload.Type\n\n/**\n * A model-seat change that applies from the next turn on.\n *\n * @category models\n * @since 0.1.0\n */\nexport const SeatPayload = Schema.Struct({\n kind: Schema.Literal(\"Seat\"),\n seat: Schema.NonEmptyString\n})\n\n/**\n * The decoded form of {@link SeatPayload}.\n *\n * @category models\n * @since 0.1.0\n */\nexport type SeatPayload = typeof SeatPayload.Type\n\n/**\n * A thinking-level change that applies from the next turn on.\n *\n * @category models\n * @since 0.1.0\n */\nexport const ThinkingPayload = Schema.Struct({\n kind: Schema.Literal(\"Thinking\"),\n thinking: Thinking\n})\n\n/**\n * The decoded form of {@link ThinkingPayload}.\n *\n * @category models\n * @since 0.1.0\n */\nexport type ThinkingPayload = typeof ThinkingPayload.Type\n\n/**\n * Tools added to the active set for future turns.\n *\n * Additive only: steering can widen what the agent may reach for, and cannot\n * narrow it, because a turn already in flight may hold a call to a tool a\n * narrowing steer would have removed.\n *\n * @category models\n * @since 0.1.0\n */\nexport const ToolsPayload = Schema.Struct({\n kind: Schema.Literal(\"Tools\"),\n toolNames: Schema.NonEmptyArray(Schema.NonEmptyString)\n})\n\n/**\n * The decoded form of {@link ToolsPayload}.\n *\n * @category models\n * @since 0.1.0\n */\nexport type ToolsPayload = typeof ToolsPayload.Type\n\n/**\n * Any steering item a notification payload can carry.\n *\n * @category models\n * @since 0.1.0\n */\nexport const SteerPayload = Schema.Union([MessagePayload, SeatPayload, ThinkingPayload, ToolsPayload])\n\n/**\n * Any steering item a notification payload can carry.\n *\n * @category models\n * @since 0.1.0\n */\nexport type SteerPayload = typeof SteerPayload.Type\n\nconst parse = Schema.decodeUnknownOption(SteerPayload)\n\nconst record = (value: unknown): Readonly<Record<string, unknown>> | undefined =>\n typeof value === \"object\" && value !== null && !Array.isArray(value)\n ? value as Readonly<Record<string, unknown>>\n : undefined\n\n/**\n * Reads a notification as a steering item, or reports that it is not one.\n *\n * The notification, not its payload, is the argument: a `system-event` is\n * never a steer, so the classification cannot be separated from who sent it.\n *\n * @param notification the admitted notification\n * @category conversions\n * @since 0.1.0\n */\nexport const decode = (notification: Notification.Notification): SteerPayload | undefined => {\n if (notification._tag === \"system-event\") return undefined\n const fields = record(notification.payload)\n if (fields === undefined) return undefined\n // A record with a body and no kind is a message: it is the shape a caller\n // writes when it has nothing else to say.\n const candidate = fields[\"kind\"] === undefined && typeof fields[\"body\"] === \"string\"\n ? { kind: \"Message\", body: fields[\"body\"] }\n : fields\n const decoded = parse(candidate)\n return decoded._tag === \"Some\" ? decoded.value : undefined\n}\n\n/**\n * The payload a steering item is stored as.\n *\n * Every item is written with its `kind`, including a message: the body-only\n * form is read as a convenience and never written.\n *\n * The result is typed as the item itself. Every item is JSON, so it is\n * assignable to `Notification.payload` without an assertion.\n *\n * The returned record shares no mutable structure with `item`. A caller hands\n * the result to an admission that serializes it later, so an array still\n * aliased to the caller could change what is durably journaled after the call\n * returned.\n *\n * @param item the steering item\n * @category conversions\n * @since 0.1.0\n */\nexport const encode = (item: SteerPayload): SteerPayload =>\n item.kind === \"Tools\" ? { ...item, toolNames: [...item.toolNames] } : { ...item }\n"]}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Durable notification queue services.
3
+ *
4
+ * @since 0.1.0
5
+ */
6
+ /**
7
+ * @category services
8
+ * @since 0.1.0
9
+ */
10
+ export * as Alerts from "./Alerts.ts";
11
+ /**
12
+ * @category models
13
+ * @since 0.1.0
14
+ */
15
+ export * as Notification from "./Notification.ts";
16
+ /**
17
+ * @category events
18
+ * @since 0.1.0
19
+ */
20
+ export * as NotificationEvent from "./NotificationEvent.ts";
21
+ /**
22
+ * @category models
23
+ * @since 0.1.0
24
+ */
25
+ export * as NotificationState from "./NotificationState.ts";
26
+ /**
27
+ * @category services
28
+ * @since 0.1.0
29
+ */
30
+ export * as NotificationQueue from "./NotificationQueue.ts";
31
+ /**
32
+ * @category projections
33
+ * @since 0.1.0
34
+ */
35
+ export * as Projection from "./Projection.ts";
36
+ /**
37
+ * @category models
38
+ * @since 0.1.0
39
+ */
40
+ export * as SteerPayload from "./SteerPayload.ts";
41
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;GAGG;AACH,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAErC;;;GAGG;AACH,OAAO,KAAK,YAAY,MAAM,mBAAmB,CAAA;AAEjD;;;GAGG;AACH,OAAO,KAAK,iBAAiB,MAAM,wBAAwB,CAAA;AAE3D;;;GAGG;AACH,OAAO,KAAK,iBAAiB,MAAM,wBAAwB,CAAA;AAE3D;;;GAGG;AACH,OAAO,KAAK,iBAAiB,MAAM,wBAAwB,CAAA;AAE3D;;;GAGG;AACH,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAE7C;;;GAGG;AACH,OAAO,KAAK,YAAY,MAAM,mBAAmB,CAAA"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Durable notification queue services.
3
+ *
4
+ * @since 0.1.0
5
+ */
6
+ /**
7
+ * @category services
8
+ * @since 0.1.0
9
+ */
10
+ export * as Alerts from "./Alerts.js";
11
+ /**
12
+ * @category models
13
+ * @since 0.1.0
14
+ */
15
+ export * as Notification from "./Notification.js";
16
+ /**
17
+ * @category events
18
+ * @since 0.1.0
19
+ */
20
+ export * as NotificationEvent from "./NotificationEvent.js";
21
+ /**
22
+ * @category models
23
+ * @since 0.1.0
24
+ */
25
+ export * as NotificationState from "./NotificationState.js";
26
+ /**
27
+ * @category services
28
+ * @since 0.1.0
29
+ */
30
+ export * as NotificationQueue from "./NotificationQueue.js";
31
+ /**
32
+ * @category projections
33
+ * @since 0.1.0
34
+ */
35
+ export * as Projection from "./Projection.js";
36
+ /**
37
+ * @category models
38
+ * @since 0.1.0
39
+ */
40
+ export * as SteerPayload from "./SteerPayload.js";
41
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;GAGG;AACH,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAErC;;;GAGG;AACH,OAAO,KAAK,YAAY,MAAM,mBAAmB,CAAA;AAEjD;;;GAGG;AACH,OAAO,KAAK,iBAAiB,MAAM,wBAAwB,CAAA;AAE3D;;;GAGG;AACH,OAAO,KAAK,iBAAiB,MAAM,wBAAwB,CAAA;AAE3D;;;GAGG;AACH,OAAO,KAAK,iBAAiB,MAAM,wBAAwB,CAAA;AAE3D;;;GAGG;AACH,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAE7C;;;GAGG;AACH,OAAO,KAAK,YAAY,MAAM,mBAAmB,CAAA","sourcesContent":["/**\n * Durable notification queue services.\n *\n * @since 0.1.0\n */\n\n/**\n * @category services\n * @since 0.1.0\n */\nexport * as Alerts from \"./Alerts.ts\"\n\n/**\n * @category models\n * @since 0.1.0\n */\nexport * as Notification from \"./Notification.ts\"\n\n/**\n * @category events\n * @since 0.1.0\n */\nexport * as NotificationEvent from \"./NotificationEvent.ts\"\n\n/**\n * @category models\n * @since 0.1.0\n */\nexport * as NotificationState from \"./NotificationState.ts\"\n\n/**\n * @category services\n * @since 0.1.0\n */\nexport * as NotificationQueue from \"./NotificationQueue.ts\"\n\n/**\n * @category projections\n * @since 0.1.0\n */\nexport * as Projection from \"./Projection.ts\"\n\n/**\n * @category models\n * @since 0.1.0\n */\nexport * as SteerPayload from \"./SteerPayload.ts\"\n"]}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Bounded, scan-resistant retention for the per-run folds a journal reader
3
+ * keeps in memory.
4
+ *
5
+ * @private
6
+ * @since 1.0.0
7
+ */
8
+ /**
9
+ * A bounded per-run fold cache.
10
+ *
11
+ * @private
12
+ * @since 1.0.0
13
+ */
14
+ export interface FoldCache<A> {
15
+ readonly get: (key: string) => A | undefined;
16
+ readonly put: (key: string, value: A) => void;
17
+ }
18
+ /**
19
+ * A bounded map of per-run folds that a sweep wider than the bound cannot
20
+ * empty.
21
+ *
22
+ * Least-recently-used eviction has a zero hit rate under exactly the workload
23
+ * a supervisor produces: an ordered sweep of `bound + 1` runs evicts the fold
24
+ * the next read is about to want, so every poll replays every journal from
25
+ * sequence zero instead of reading its tail. Under pressure this instead
26
+ * sacrifices the NEWEST fold no later read has reused, so a sweep of any width
27
+ * keeps every fold it has read twice and pays for its width in a fixed handful
28
+ * of refolds. Only when every retained fold has been reused does it fall back
29
+ * to evicting the oldest, and it then clears the reuse marks so a fold cannot
30
+ * hold a slot on one ancient read forever.
31
+ *
32
+ * @private
33
+ * @since 1.0.0
34
+ */
35
+ export declare const make: <A>(bound: number) => FoldCache<A>;
36
+ //# sourceMappingURL=foldCache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"foldCache.d.ts","sourceRoot":"","sources":["../../../src/internal/foldCache.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH;;;;;GAKG;AACH,MAAM,WAAW,SAAS,CAAC,CAAC;IAC1B,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,CAAC,GAAG,SAAS,CAAA;IAC5C,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,KAAK,IAAI,CAAA;CAC9C;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,IAAI,GAAI,CAAC,EAAE,OAAO,MAAM,KAAG,SAAS,CAAC,CAAC,CA6BlD,CAAA"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Bounded, scan-resistant retention for the per-run folds a journal reader
3
+ * keeps in memory.
4
+ *
5
+ * @private
6
+ * @since 1.0.0
7
+ */
8
+ /**
9
+ * A bounded map of per-run folds that a sweep wider than the bound cannot
10
+ * empty.
11
+ *
12
+ * Least-recently-used eviction has a zero hit rate under exactly the workload
13
+ * a supervisor produces: an ordered sweep of `bound + 1` runs evicts the fold
14
+ * the next read is about to want, so every poll replays every journal from
15
+ * sequence zero instead of reading its tail. Under pressure this instead
16
+ * sacrifices the NEWEST fold no later read has reused, so a sweep of any width
17
+ * keeps every fold it has read twice and pays for its width in a fixed handful
18
+ * of refolds. Only when every retained fold has been reused does it fall back
19
+ * to evicting the oldest, and it then clears the reuse marks so a fold cannot
20
+ * hold a slot on one ancient read forever.
21
+ *
22
+ * @private
23
+ * @since 1.0.0
24
+ */
25
+ export const make = (bound) => {
26
+ const held = new Map();
27
+ return {
28
+ get: (key) => {
29
+ const entry = held.get(key);
30
+ if (entry === undefined)
31
+ return undefined;
32
+ entry.reused = true;
33
+ return entry.value;
34
+ },
35
+ put: (key, value) => {
36
+ const existing = held.get(key);
37
+ if (existing !== undefined) {
38
+ existing.value = value;
39
+ return;
40
+ }
41
+ if (held.size >= bound) {
42
+ let victim;
43
+ for (const [candidate, entry] of held) {
44
+ if (!entry.reused)
45
+ victim = candidate;
46
+ }
47
+ if (victim === undefined) {
48
+ victim = held.keys().next().value;
49
+ for (const entry of held.values())
50
+ entry.reused = false;
51
+ }
52
+ held.delete(victim);
53
+ }
54
+ held.set(key, { value, reused: false });
55
+ }
56
+ };
57
+ };
58
+ //# sourceMappingURL=foldCache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"foldCache.js","sourceRoot":"","sources":["../../../src/internal/foldCache.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAmBH;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAI,KAAa,EAAgB,EAAE;IACrD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAmB,CAAA;IACvC,OAAO;QACL,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE;YACX,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YAC3B,IAAI,KAAK,KAAK,SAAS;gBAAE,OAAO,SAAS,CAAA;YACzC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAA;YACnB,OAAO,KAAK,CAAC,KAAK,CAAA;QACpB,CAAC;QACD,GAAG,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;YAClB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YAC9B,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAC3B,QAAQ,CAAC,KAAK,GAAG,KAAK,CAAA;gBACtB,OAAM;YACR,CAAC;YACD,IAAI,IAAI,CAAC,IAAI,IAAI,KAAK,EAAE,CAAC;gBACvB,IAAI,MAA0B,CAAA;gBAC9B,KAAK,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC;oBACtC,IAAI,CAAC,KAAK,CAAC,MAAM;wBAAE,MAAM,GAAG,SAAS,CAAA;gBACvC,CAAC;gBACD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;oBACzB,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAM,CAAA;oBAClC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE;wBAAE,KAAK,CAAC,MAAM,GAAG,KAAK,CAAA;gBACzD,CAAC;gBACD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;YACrB,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAA;QACzC,CAAC;KACF,CAAA;AACH,CAAC,CAAA","sourcesContent":["/**\n * Bounded, scan-resistant retention for the per-run folds a journal reader\n * keeps in memory.\n *\n * @private\n * @since 1.0.0\n */\n\n/** One retained fold, and whether a later read has come back for it. */\ninterface Held<A> {\n value: A\n reused: boolean\n}\n\n/**\n * A bounded per-run fold cache.\n *\n * @private\n * @since 1.0.0\n */\nexport interface FoldCache<A> {\n readonly get: (key: string) => A | undefined\n readonly put: (key: string, value: A) => void\n}\n\n/**\n * A bounded map of per-run folds that a sweep wider than the bound cannot\n * empty.\n *\n * Least-recently-used eviction has a zero hit rate under exactly the workload\n * a supervisor produces: an ordered sweep of `bound + 1` runs evicts the fold\n * the next read is about to want, so every poll replays every journal from\n * sequence zero instead of reading its tail. Under pressure this instead\n * sacrifices the NEWEST fold no later read has reused, so a sweep of any width\n * keeps every fold it has read twice and pays for its width in a fixed handful\n * of refolds. Only when every retained fold has been reused does it fall back\n * to evicting the oldest, and it then clears the reuse marks so a fold cannot\n * hold a slot on one ancient read forever.\n *\n * @private\n * @since 1.0.0\n */\nexport const make = <A>(bound: number): FoldCache<A> => {\n const held = new Map<string, Held<A>>()\n return {\n get: (key) => {\n const entry = held.get(key)\n if (entry === undefined) return undefined\n entry.reused = true\n return entry.value\n },\n put: (key, value) => {\n const existing = held.get(key)\n if (existing !== undefined) {\n existing.value = value\n return\n }\n if (held.size >= bound) {\n let victim: string | undefined\n for (const [candidate, entry] of held) {\n if (!entry.reused) victim = candidate\n }\n if (victim === undefined) {\n victim = held.keys().next().value!\n for (const entry of held.values()) entry.reused = false\n }\n held.delete(victim)\n }\n held.set(key, { value, reused: false })\n }\n }\n}\n"]}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The one replay rule for an admission record, shared by the queue's fold and
3
+ * the journal projection so the two read the same history to the same state.
4
+ *
5
+ * @private
6
+ * @since 1.0.0
7
+ */
8
+ import type * as NotificationEvent from "../NotificationEvent.ts";
9
+ import * as NotificationState from "../NotificationState.ts";
10
+ /**
11
+ * Applies one committed admission record to the state so far.
12
+ *
13
+ * A versioned record whose id is still pending is an edit: it replaces that
14
+ * item in place and keeps its original sequence, so the edit is delivered at
15
+ * the cutoff the first version was admitted under. Every other record applies
16
+ * the decision it committed.
17
+ *
18
+ * @private
19
+ * @since 1.0.0
20
+ */
21
+ export declare const applyAdmitted: (state: NotificationState.State, event: NotificationEvent.Admitted, seq: number) => NotificationState.State;
22
+ //# sourceMappingURL=replay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../../../src/internal/replay.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,KAAK,iBAAiB,MAAM,yBAAyB,CAAA;AACjE,OAAO,KAAK,iBAAiB,MAAM,yBAAyB,CAAA;AAE5D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa,GACxB,OAAO,iBAAiB,CAAC,KAAK,EAC9B,OAAO,iBAAiB,CAAC,QAAQ,EACjC,KAAK,MAAM,KACV,iBAAiB,CAAC,KAYpB,CAAA"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The one replay rule for an admission record, shared by the queue's fold and
3
+ * the journal projection so the two read the same history to the same state.
4
+ *
5
+ * @private
6
+ * @since 1.0.0
7
+ */
8
+ import * as NotificationState from "../NotificationState.js";
9
+ /**
10
+ * Applies one committed admission record to the state so far.
11
+ *
12
+ * A versioned record whose id is still pending is an edit: it replaces that
13
+ * item in place and keeps its original sequence, so the edit is delivered at
14
+ * the cutoff the first version was admitted under. Every other record applies
15
+ * the decision it committed.
16
+ *
17
+ * @private
18
+ * @since 1.0.0
19
+ */
20
+ export const applyAdmitted = (state, event, seq) => {
21
+ if (event.version !== undefined && state.items.some((item) => item.notification.id === event.notification.id)) {
22
+ return Object.freeze({
23
+ ...state,
24
+ items: Object.freeze(state.items.map((item) => item.notification.id === event.notification.id
25
+ ? Object.freeze({ notification: event.notification, seq: item.seq })
26
+ : item))
27
+ });
28
+ }
29
+ return NotificationState.applyAdmission(state, event.notification, seq, event.decision);
30
+ };
31
+ //# sourceMappingURL=replay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.js","sourceRoot":"","sources":["../../../src/internal/replay.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,iBAAiB,MAAM,yBAAyB,CAAA;AAE5D;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,KAA8B,EAC9B,KAAiC,EACjC,GAAW,EACc,EAAE;IAC3B,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,KAAK,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC,EAAE,CAAC;QAC9G,OAAO,MAAM,CAAC,MAAM,CAAC;YACnB,GAAG,KAAK;YACR,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAC5C,IAAI,CAAC,YAAY,CAAC,EAAE,KAAK,KAAK,CAAC,YAAY,CAAC,EAAE;gBAC5C,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC;gBACpE,CAAC,CAAC,IAAI,CACT,CAAC;SACH,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,iBAAiB,CAAC,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,YAAY,EAAE,GAAG,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;AACzF,CAAC,CAAA","sourcesContent":["/**\n * The one replay rule for an admission record, shared by the queue's fold and\n * the journal projection so the two read the same history to the same state.\n *\n * @private\n * @since 1.0.0\n */\n\nimport type * as NotificationEvent from \"../NotificationEvent.ts\"\nimport * as NotificationState from \"../NotificationState.ts\"\n\n/**\n * Applies one committed admission record to the state so far.\n *\n * A versioned record whose id is still pending is an edit: it replaces that\n * item in place and keeps its original sequence, so the edit is delivered at\n * the cutoff the first version was admitted under. Every other record applies\n * the decision it committed.\n *\n * @private\n * @since 1.0.0\n */\nexport const applyAdmitted = (\n state: NotificationState.State,\n event: NotificationEvent.Admitted,\n seq: number\n): NotificationState.State => {\n if (event.version !== undefined && state.items.some((item) => item.notification.id === event.notification.id)) {\n return Object.freeze({\n ...state,\n items: Object.freeze(state.items.map((item) =>\n item.notification.id === event.notification.id\n ? Object.freeze({ notification: event.notification, seq: item.seq })\n : item\n ))\n })\n }\n return NotificationState.applyAdmission(state, event.notification, seq, event.decision)\n}\n"]}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The webhook alert sink: one POST per alert, keyed so the receiver can
3
+ * dedupe on it.
4
+ *
5
+ * The transport rules live here rather than beside the policy, because which
6
+ * endpoint is paged, over which scheme, carrying which credential is a
7
+ * deployment's decision and the delays are not.
8
+ *
9
+ * @since 1.0.0
10
+ */
11
+ import { Duration, Layer } from "effect";
12
+ import { Sink } from "./AlertSink.ts";
13
+ /**
14
+ * How long the webhook sink waits for an answer before it calls the page
15
+ * refused.
16
+ *
17
+ * @category constants
18
+ * @since 1.0.0
19
+ */
20
+ export declare const defaultWebhookTimeout: Duration.Duration;
21
+ /**
22
+ * A sink that POSTs each alert to one webhook.
23
+ *
24
+ * The body is the alert plus its {@link alertId}, and the same id is sent as
25
+ * an `Idempotency-Key` header, because the package requires the receiver to
26
+ * dedupe on it and a body without it cannot. The header is set after the
27
+ * caller's headers, so it is the one this sink sends.
28
+ *
29
+ * The endpoint must be an absolute `http:` or `https:` URL, checked when the
30
+ * layer is built, and the request is made with redirect following disabled:
31
+ * the caller's headers can carry a credential, and a followed redirect would
32
+ * re-send them to an origin the deployment never named. A 3xx answer is a
33
+ * refusal like any other non-2xx, not a forwarding instruction. The sink owns
34
+ * its Fetch transport; an externally supplied `HttpClient` is not used.
35
+ * Each delivery has its own scope, which aborts the request and releases any
36
+ * unread response body on completion, failure, timeout, or interruption.
37
+ *
38
+ * The url is treated as a credential, because a webhook's secret usually is
39
+ * its path or query. The delivery's HTTP span is named for the method and
40
+ * records the scheme, host, port and answering status, never the path, the
41
+ * query or userinfo, and it fails with the same url-free reason the sink
42
+ * returns. A host that records HTTP headers on spans gets every name in
43
+ * `headers`, and a redirect's `location`, as `<redacted>`.
44
+ *
45
+ * A non-2xx response is a failure, not a delivery, and an endpoint that never
46
+ * answers is a failure after `timeout`. Paging is exactly the case where a
47
+ * silently dropped request is worse than a retry, and a hung request is worse
48
+ * than either: it is indistinguishable from silence, so it must not be
49
+ * possible to wait on one forever.
50
+ *
51
+ * @param options the endpoint, any headers it needs, and how long to wait
52
+ * @category layers
53
+ * @since 0.1.0
54
+ */
55
+ export declare const layerWebhook: (options: {
56
+ readonly url: string;
57
+ readonly headers?: Readonly<Record<string, string>> | undefined;
58
+ readonly timeout?: Duration.Duration | undefined;
59
+ }) => Layer.Layer<Sink>;
60
+ //# sourceMappingURL=layerWebhook.d.ts.map