@smthrs/notifications 0.0.0-stage → 1.0.0-rc.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +127 -0
- package/LICENSE +21 -0
- package/README.md +93 -2
- package/dist/cjs/AlertPolicy.d.ts +284 -0
- package/dist/cjs/AlertPolicy.d.ts.map +1 -0
- package/dist/cjs/AlertPolicy.js +124 -0
- package/dist/cjs/AlertPolicy.js.map +7 -0
- package/dist/cjs/AlertRuntime.d.ts +90 -0
- package/dist/cjs/AlertRuntime.d.ts.map +1 -0
- package/dist/cjs/AlertRuntime.js +166 -0
- package/dist/cjs/AlertRuntime.js.map +7 -0
- package/dist/cjs/AlertSink.d.ts +101 -0
- package/dist/cjs/AlertSink.d.ts.map +1 -0
- package/dist/cjs/AlertSink.js +44 -0
- package/dist/cjs/AlertSink.js.map +7 -0
- package/dist/cjs/Alerts.d.ts +57 -0
- package/dist/cjs/Alerts.d.ts.map +1 -0
- package/dist/cjs/Alerts.js +46 -0
- package/dist/cjs/Alerts.js.map +7 -0
- package/dist/cjs/Notification.d.ts +214 -0
- package/dist/cjs/Notification.d.ts.map +1 -0
- package/dist/cjs/Notification.js +62 -0
- package/dist/cjs/Notification.js.map +7 -0
- package/dist/cjs/NotificationEvent.d.ts +214 -0
- package/dist/cjs/NotificationEvent.d.ts.map +1 -0
- package/dist/cjs/NotificationEvent.js +84 -0
- package/dist/cjs/NotificationEvent.js.map +7 -0
- package/dist/cjs/NotificationQueue.d.ts +194 -0
- package/dist/cjs/NotificationQueue.d.ts.map +1 -0
- package/dist/cjs/NotificationQueue.js +477 -0
- package/dist/cjs/NotificationQueue.js.map +7 -0
- package/dist/cjs/NotificationState.d.ts +169 -0
- package/dist/cjs/NotificationState.d.ts.map +1 -0
- package/dist/cjs/NotificationState.js +110 -0
- package/dist/cjs/NotificationState.js.map +7 -0
- package/dist/cjs/Projection.d.ts +30 -0
- package/dist/cjs/Projection.d.ts.map +1 -0
- package/dist/cjs/Projection.js +53 -0
- package/dist/cjs/Projection.js.map +7 -0
- package/dist/cjs/SteerPayload.d.ts +177 -0
- package/dist/cjs/SteerPayload.d.ts.map +1 -0
- package/dist/cjs/SteerPayload.js +61 -0
- package/dist/cjs/SteerPayload.js.map +7 -0
- package/dist/cjs/index.d.ts +41 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +47 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/internal/foldCache.d.ts +36 -0
- package/dist/cjs/internal/foldCache.d.ts.map +1 -0
- package/dist/cjs/internal/foldCache.js +54 -0
- package/dist/cjs/internal/foldCache.js.map +7 -0
- package/dist/cjs/internal/replay.d.ts +22 -0
- package/dist/cjs/internal/replay.d.ts.map +1 -0
- package/dist/cjs/internal/replay.js +46 -0
- package/dist/cjs/internal/replay.js.map +7 -0
- package/dist/cjs/layerWebhook.d.ts +60 -0
- package/dist/cjs/layerWebhook.d.ts.map +1 -0
- package/dist/cjs/layerWebhook.js +145 -0
- package/dist/cjs/layerWebhook.js.map +7 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/AlertPolicy.d.ts +284 -0
- package/dist/esm/AlertPolicy.d.ts.map +1 -0
- package/dist/esm/AlertPolicy.js +284 -0
- package/dist/esm/AlertPolicy.js.map +1 -0
- package/dist/esm/AlertRuntime.d.ts +90 -0
- package/dist/esm/AlertRuntime.d.ts.map +1 -0
- package/dist/esm/AlertRuntime.js +191 -0
- package/dist/esm/AlertRuntime.js.map +1 -0
- package/dist/esm/AlertSink.d.ts +101 -0
- package/dist/esm/AlertSink.d.ts.map +1 -0
- package/dist/esm/AlertSink.js +63 -0
- package/dist/esm/AlertSink.js.map +1 -0
- package/dist/esm/Alerts.d.ts +57 -0
- package/dist/esm/Alerts.d.ts.map +1 -0
- package/dist/esm/Alerts.js +57 -0
- package/dist/esm/Alerts.js.map +1 -0
- package/dist/esm/Notification.d.ts +214 -0
- package/dist/esm/Notification.d.ts.map +1 -0
- package/dist/esm/Notification.js +121 -0
- package/dist/esm/Notification.js.map +1 -0
- package/dist/esm/NotificationEvent.d.ts +214 -0
- package/dist/esm/NotificationEvent.d.ts.map +1 -0
- package/dist/esm/NotificationEvent.js +158 -0
- package/dist/esm/NotificationEvent.js.map +1 -0
- package/dist/esm/NotificationQueue.d.ts +194 -0
- package/dist/esm/NotificationQueue.d.ts.map +1 -0
- package/dist/esm/NotificationQueue.js +625 -0
- package/dist/esm/NotificationQueue.js.map +1 -0
- package/dist/esm/NotificationState.d.ts +169 -0
- package/dist/esm/NotificationState.d.ts.map +1 -0
- package/dist/esm/NotificationState.js +191 -0
- package/dist/esm/NotificationState.js.map +1 -0
- package/dist/esm/Projection.d.ts +30 -0
- package/dist/esm/Projection.d.ts.map +1 -0
- package/dist/esm/Projection.js +47 -0
- package/dist/esm/Projection.js.map +1 -0
- package/dist/esm/SteerPayload.d.ts +177 -0
- package/dist/esm/SteerPayload.d.ts.map +1 -0
- package/dist/esm/SteerPayload.js +139 -0
- package/dist/esm/SteerPayload.js.map +1 -0
- package/dist/esm/index.d.ts +41 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +41 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/foldCache.d.ts +36 -0
- package/dist/esm/internal/foldCache.d.ts.map +1 -0
- package/dist/esm/internal/foldCache.js +58 -0
- package/dist/esm/internal/foldCache.js.map +1 -0
- package/dist/esm/internal/replay.d.ts +22 -0
- package/dist/esm/internal/replay.d.ts.map +1 -0
- package/dist/esm/internal/replay.js +31 -0
- package/dist/esm/internal/replay.js.map +1 -0
- package/dist/esm/layerWebhook.d.ts +60 -0
- package/dist/esm/layerWebhook.d.ts.map +1 -0
- package/dist/esm/layerWebhook.js +186 -0
- package/dist/esm/layerWebhook.js.map +1 -0
- package/docs/README.md +146 -0
- package/docs/api.md +443 -0
- package/docs/concepts/admission-and-promotion.md +144 -0
- package/docs/concepts/alerting.md +138 -0
- package/docs/concepts/journal-records.md +132 -0
- package/docs/guides/alert-on-a-stuck-run.md +140 -0
- package/docs/guides/drain-at-a-turn-boundary.md +95 -0
- package/docs/guides/handle-a-full-queue.md +102 -0
- package/docs/guides/report-pending-notifications.md +92 -0
- package/docs/guides/send-alerts-to-a-webhook.md +155 -0
- package/docs/guides/steer-a-run.md +127 -0
- package/docs/guides/testing.md +161 -0
- package/docs/installation.md +123 -0
- package/docs/quickstart.md +159 -0
- package/docs/troubleshooting.md +254 -0
- package/package.json +182 -3
- package/src/AlertPolicy.ts +377 -0
- package/src/AlertRuntime.ts +280 -0
- package/src/AlertSink.ts +105 -0
- package/src/Alerts.ts +71 -0
- package/src/Notification.ts +171 -0
- package/src/NotificationEvent.ts +213 -0
- package/src/NotificationQueue.ts +856 -0
- package/src/NotificationState.ts +295 -0
- package/src/Projection.ts +48 -0
- package/src/SteerPayload.ts +198 -0
- package/src/index.ts +47 -0
- package/src/internal/foldCache.ts +72 -0
- package/src/internal/replay.ts +39 -0
- 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":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAG/B;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AAU9F;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC;IAC1C,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC,MAAM;CACpB,CAAC,CAAA;AAUF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC;IACvC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC,cAAc;CAC5B,CAAC,CAAA;AAUF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,CAAC;IAC3C,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC;IAChC,QAAQ,EAAE,QAAQ;CACnB,CAAC,CAAA;AAUF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC;IACxC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,cAAc,CAAC;CACvD,CAAC,CAAA;AAUF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,cAAc,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,CAAC,CAAC,CAAA;AAUtG,MAAM,KAAK,GAAG,MAAM,CAAC,mBAAmB,CAAC,YAAY,CAAC,CAAA;AAEtD,MAAM,MAAM,GAAG,CAAC,KAAc,EAAiD,EAAE,CAC/E,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;IAClE,CAAC,CAAC,KAA0C;IAC5C,CAAC,CAAC,SAAS,CAAA;AAEf;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,YAAuC,EAA4B,EAAE;IAC1F,IAAI,YAAY,CAAC,IAAI,KAAK,cAAc;QAAE,OAAO,SAAS,CAAA;IAC1D,MAAM,MAAM,GAAG,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,CAAA;IAC3C,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IAC1C,0EAA0E;IAC1E,0CAA0C;IAC1C,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,SAAS,IAAI,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,QAAQ;QAClF,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE;QAC3C,CAAC,CAAC,MAAM,CAAA;IACV,MAAM,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,CAAA;IAChC,OAAO,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;AAC5D,CAAC,CAAA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,IAAkB,EAAgB,EAAE,CACzD,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,SAAS,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,CAAA","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
|