@crewhaus/gateway-protocol 0.3.2 → 0.4.2

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/dist/index.d.ts CHANGED
@@ -231,13 +231,125 @@ export declare const AuditTailParams: z.ZodObject<{
231
231
  sinceTs?: number | undefined;
232
232
  }>;
233
233
  export type AuditTailParamsT = z.infer<typeof AuditTailParams>;
234
- export declare const Method: z.ZodEnum<["runs.create", "runs.continue", "runs.cancel", "runs.subscribe", "sessions.list", "sessions.fork", "audit.tail"]>;
234
+ /** Mirror of `@crewhaus/feedback-distill`'s `MAX_FEEDBACK_TEXT`. Kept in sync
235
+ * by hand for the same reason as SESSION_ID_REGEX above; the distill side
236
+ * still clips, so a drift can only ever be stricter here. */
237
+ export declare const MAX_FEEDBACK_TEXT = 8192;
238
+ export declare const FeedbackSubmitParams: z.ZodEffects<z.ZodObject<{
239
+ sessionId: z.ZodString;
240
+ /** 1-based ordinal of the rated user-text turn. */
241
+ turnNumber: z.ZodNumber;
242
+ thumbs: z.ZodOptional<z.ZodEnum<["up", "down"]>>;
243
+ stars: z.ZodOptional<z.ZodNumber>;
244
+ scale: z.ZodOptional<z.ZodObject<{
245
+ value: z.ZodNumber;
246
+ min: z.ZodNumber;
247
+ max: z.ZodNumber;
248
+ }, "strict", z.ZodTypeAny, {
249
+ value: number;
250
+ min: number;
251
+ max: number;
252
+ }, {
253
+ value: number;
254
+ min: number;
255
+ max: number;
256
+ }>>;
257
+ comment: z.ZodOptional<z.ZodString>;
258
+ /** A better answer — becomes `expected_output` at distill time. */
259
+ correction: z.ZodOptional<z.ZodString>;
260
+ /** Opaque rater handle. Redacted at ingestion like every other free text. */
261
+ rater: z.ZodOptional<z.ZodString>;
262
+ /** B19 — this record settles a multi-rater disagreement. */
263
+ adjudication: z.ZodOptional<z.ZodBoolean>;
264
+ }, "strict", z.ZodTypeAny, {
265
+ sessionId: string;
266
+ turnNumber: number;
267
+ thumbs?: "up" | "down" | undefined;
268
+ stars?: number | undefined;
269
+ scale?: {
270
+ value: number;
271
+ min: number;
272
+ max: number;
273
+ } | undefined;
274
+ comment?: string | undefined;
275
+ correction?: string | undefined;
276
+ rater?: string | undefined;
277
+ adjudication?: boolean | undefined;
278
+ }, {
279
+ sessionId: string;
280
+ turnNumber: number;
281
+ thumbs?: "up" | "down" | undefined;
282
+ stars?: number | undefined;
283
+ scale?: {
284
+ value: number;
285
+ min: number;
286
+ max: number;
287
+ } | undefined;
288
+ comment?: string | undefined;
289
+ correction?: string | undefined;
290
+ rater?: string | undefined;
291
+ adjudication?: boolean | undefined;
292
+ }>, {
293
+ sessionId: string;
294
+ turnNumber: number;
295
+ thumbs?: "up" | "down" | undefined;
296
+ stars?: number | undefined;
297
+ scale?: {
298
+ value: number;
299
+ min: number;
300
+ max: number;
301
+ } | undefined;
302
+ comment?: string | undefined;
303
+ correction?: string | undefined;
304
+ rater?: string | undefined;
305
+ adjudication?: boolean | undefined;
306
+ }, {
307
+ sessionId: string;
308
+ turnNumber: number;
309
+ thumbs?: "up" | "down" | undefined;
310
+ stars?: number | undefined;
311
+ scale?: {
312
+ value: number;
313
+ min: number;
314
+ max: number;
315
+ } | undefined;
316
+ comment?: string | undefined;
317
+ correction?: string | undefined;
318
+ rater?: string | undefined;
319
+ adjudication?: boolean | undefined;
320
+ }>;
321
+ export declare const FeedbackSubmitResult: z.ZodObject<{
322
+ recorded: z.ZodBoolean;
323
+ id: z.ZodString;
324
+ }, "strict", z.ZodTypeAny, {
325
+ id: string;
326
+ recorded: boolean;
327
+ }, {
328
+ id: string;
329
+ recorded: boolean;
330
+ }>;
331
+ export type FeedbackSubmitParamsT = z.infer<typeof FeedbackSubmitParams>;
332
+ export type FeedbackSubmitResultT = z.infer<typeof FeedbackSubmitResult>;
333
+ export declare const Method: z.ZodEnum<["runs.create", "runs.continue", "runs.cancel", "runs.subscribe", "sessions.list", "sessions.fork", "audit.tail", "feedback.submit"]>;
235
334
  export type MethodT = z.infer<typeof Method>;
236
335
  export declare function decodeRequest(raw: unknown): RequestEnvelopeT & {
237
336
  method: MethodT;
238
337
  };
239
338
  export declare function encodeSuccess(id: string, result: unknown): ResponseEnvelopeT;
240
339
  export declare function encodeError(id: string, code: string, message: string, data?: unknown): ResponseEnvelopeT;
340
+ /** MIME type of a `runs.subscribe` response body. */
341
+ export declare const SSE_CONTENT_TYPE: "text/event-stream";
342
+ /**
343
+ * Encode one trace event as an SSE `data:` frame. `event` is serialized with
344
+ * `JSON.stringify` (a TraceEvent is always JSON-serializable), so the frame is
345
+ * a single `data:` line terminated by the mandatory blank line.
346
+ */
347
+ export declare function encodeSseEvent(event: unknown): string;
348
+ /**
349
+ * Encode an SSE comment frame (heartbeat / open marker). Any newline in `text`
350
+ * is collapsed to a space so the comment stays a single well-formed frame.
351
+ */
352
+ export declare function encodeSseComment(text: string): string;
241
353
  export declare const ErrorCode: {
242
354
  readonly Unauthorized: "unauthorized";
243
355
  readonly Forbidden: "forbidden";
package/dist/index.js CHANGED
@@ -116,6 +116,63 @@ export const SessionsForkResult = z.object({ newSessionId: z.string().min(1) }).
116
116
  export const AuditTailParams = z
117
117
  .object({ tenantId: z.string().min(1), sinceTs: z.number().int().nonnegative().optional() })
118
118
  .strict();
119
+ /**
120
+ * NEW-inloop-coverage — the gateway's RATING-CAPTURE method. A gateway-served
121
+ * agent is the highest-traffic surface CrewHaus has, and until this method it
122
+ * had no spec'd way to record a human verdict at all: `distill`, `optimize
123
+ * --ratings`, `judge calibrate` and the auto-distill flywheel were all
124
+ * structurally unreachable for the managed shape.
125
+ *
126
+ * The params are the USER-SUPPLIED subset of a `FeedbackRecord` (the daemon
127
+ * stamps `schemaVersion`, `id`, `source: "ui"` and `ts` itself, so a client
128
+ * cannot forge provenance or backdate a rating past the auto-distill
129
+ * watermark). `sessionId` + `turnNumber` are the same join key `crewhaus
130
+ * rate` uses, so a gateway rating and a CLI rating distill identically.
131
+ *
132
+ * The grammars are pinned HERE, at the boundary, so a bad submission is a
133
+ * protocol error instead of a durable line nothing downstream can use:
134
+ * - `sessionId` must match `@crewhaus/session-store`'s id grammar, which is
135
+ * also what `isFeedbackRecord` requires — an id in any other shape would
136
+ * be accepted, written, counted against disk, and then silently dropped
137
+ * by every reader (and it is interpolated into a path by the managed
138
+ * daemon's tenant-ownership check);
139
+ * - the free-text fields are length-capped at the same bound
140
+ * `clipFeedbackText` enforces, so oversize input is REFUSED rather than
141
+ * silently truncated on a surface whose `correction` becomes a dataset's
142
+ * `expected_output`.
143
+ */
144
+ /** `@crewhaus/session-store`'s `generateId` grammar (mirrored — this package
145
+ * is dependency-light by design and must not pull the store in). */
146
+ const SESSION_ID_REGEX = /^sess_[0-9a-f]{16}$/;
147
+ /** Mirror of `@crewhaus/feedback-distill`'s `MAX_FEEDBACK_TEXT`. Kept in sync
148
+ * by hand for the same reason as SESSION_ID_REGEX above; the distill side
149
+ * still clips, so a drift can only ever be stricter here. */
150
+ export const MAX_FEEDBACK_TEXT = 8192;
151
+ export const FeedbackSubmitParams = z
152
+ .object({
153
+ sessionId: z.string().regex(SESSION_ID_REGEX, "sessionId must look like sess_<16 hex digits>"),
154
+ /** 1-based ordinal of the rated user-text turn. */
155
+ turnNumber: z.number().int().positive(),
156
+ thumbs: z.enum(["up", "down"]).optional(),
157
+ stars: z.number().int().min(1).max(5).optional(),
158
+ scale: z.object({ value: z.number(), min: z.number(), max: z.number() }).strict().optional(),
159
+ comment: z.string().max(MAX_FEEDBACK_TEXT).optional(),
160
+ /** A better answer — becomes `expected_output` at distill time. */
161
+ correction: z.string().max(MAX_FEEDBACK_TEXT).optional(),
162
+ /** Opaque rater handle. Redacted at ingestion like every other free text. */
163
+ rater: z.string().max(MAX_FEEDBACK_TEXT).optional(),
164
+ /** B19 — this record settles a multi-rater disagreement. */
165
+ adjudication: z.boolean().optional(),
166
+ })
167
+ .strict()
168
+ .refine((p) => p.thumbs !== undefined ||
169
+ p.stars !== undefined ||
170
+ p.scale !== undefined ||
171
+ (p.comment ?? "") !== "" ||
172
+ (p.correction ?? "") !== "", { message: "feedback.submit needs at least one of thumbs/stars/scale/comment/correction" });
173
+ export const FeedbackSubmitResult = z
174
+ .object({ recorded: z.boolean(), id: z.string().min(1) })
175
+ .strict();
119
176
  export const Method = z.enum([
120
177
  "runs.create",
121
178
  "runs.continue",
@@ -124,6 +181,7 @@ export const Method = z.enum([
124
181
  "sessions.list",
125
182
  "sessions.fork",
126
183
  "audit.tail",
184
+ "feedback.submit",
127
185
  ]);
128
186
  const PARAM_SCHEMAS = {
129
187
  "runs.create": RunsCreateParams,
@@ -133,6 +191,7 @@ const PARAM_SCHEMAS = {
133
191
  "sessions.list": SessionsListParams,
134
192
  "sessions.fork": SessionsForkParams,
135
193
  "audit.tail": AuditTailParams,
194
+ "feedback.submit": FeedbackSubmitParams,
136
195
  };
137
196
  export function decodeRequest(raw) {
138
197
  const parsed = RequestEnvelope.safeParse(raw);
@@ -165,6 +224,45 @@ export function encodeError(id, code, message, data) {
165
224
  };
166
225
  }
167
226
  // ---------------------------------------------------------------------------
227
+ // `runs.subscribe` — Server-Sent Events framing.
228
+ //
229
+ // `runs.subscribe` is the ONE method that does not answer with a JSON
230
+ // `ResponseEnvelope`: it upgrades to a long-lived `text/event-stream` that
231
+ // replays the run's buffered trace events and then live-streams new ones.
232
+ // The frame format lives HERE (the wire-contract package) so the daemon and
233
+ // every reference client encode/parse it identically:
234
+ //
235
+ // - each trace event is one SSE `data:` frame carrying the event's JSON.
236
+ // `JSON.stringify` never emits a literal newline (newlines inside string
237
+ // fields are escaped to `\n`), so one event is always exactly one `data:`
238
+ // line — no multi-line-`data:` reassembly is required on the read side.
239
+ // - heartbeats and the connection-open marker are SSE COMMENT frames
240
+ // (`:`-prefixed); a spec-compliant client ignores them, so they keep
241
+ // intermediaries from idling the connection out without polluting the
242
+ // event stream. A comment body must not contain a newline.
243
+ //
244
+ // A stream carries no envelope `id`/`protocol` — those are per-request-reply
245
+ // fields; the subscription is a fire-hose keyed by the `runId` in the
246
+ // originating `runs.subscribe` request.
247
+ // ---------------------------------------------------------------------------
248
+ /** MIME type of a `runs.subscribe` response body. */
249
+ export const SSE_CONTENT_TYPE = "text/event-stream";
250
+ /**
251
+ * Encode one trace event as an SSE `data:` frame. `event` is serialized with
252
+ * `JSON.stringify` (a TraceEvent is always JSON-serializable), so the frame is
253
+ * a single `data:` line terminated by the mandatory blank line.
254
+ */
255
+ export function encodeSseEvent(event) {
256
+ return `data: ${JSON.stringify(event)}\n\n`;
257
+ }
258
+ /**
259
+ * Encode an SSE comment frame (heartbeat / open marker). Any newline in `text`
260
+ * is collapsed to a space so the comment stays a single well-formed frame.
261
+ */
262
+ export function encodeSseComment(text) {
263
+ return `: ${text.replace(/[\r\n]+/g, " ")}\n\n`;
264
+ }
265
+ // ---------------------------------------------------------------------------
168
266
  // Standard error codes — wire-stable so reference clients can switch on them.
169
267
  // ---------------------------------------------------------------------------
170
268
  export const ErrorCode = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crewhaus/gateway-protocol",
3
- "version": "0.3.2",
3
+ "version": "0.4.2",
4
4
  "type": "module",
5
5
  "description": "JSON-RPC wire protocol for the managed-daemon gateway — versioned envelope + Zod schemas",
6
6
  "main": "dist/index.js",
@@ -15,7 +15,7 @@
15
15
  "test": "bun test src"
16
16
  },
17
17
  "dependencies": {
18
- "@crewhaus/errors": "0.3.2",
18
+ "@crewhaus/errors": "0.4.2",
19
19
  "zod": "^3.23.8"
20
20
  },
21
21
  "license": "Apache-2.0",