@cursor/july 0.1.6 → 0.1.7

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 (83) hide show
  1. package/dist/bin/agent-serve.js +42 -17
  2. package/dist/channels/slack/post-update-delivery.d.ts +85 -0
  3. package/dist/channels/slack/post-update-delivery.d.ts.map +1 -0
  4. package/dist/docs/404.html +1 -1
  5. package/dist/docs/ab.html +2 -2
  6. package/dist/docs/assets/{app.DMboGKUD.js → app.COTN7wgo.js} +1 -1
  7. package/dist/docs/assets/chunks/@localSearchIndexroot.B7UcKvIn.js +1 -0
  8. package/dist/docs/assets/chunks/{VPLocalSearchBox.DkEqEnJU.js → VPLocalSearchBox.BW3TBdT0.js} +1 -1
  9. package/dist/docs/assets/chunks/{theme.ASugdMNm.js → theme.BEJW0vE7.js} +2 -2
  10. package/dist/docs/building-with-agents.html +2 -2
  11. package/dist/docs/concepts.html +2 -2
  12. package/dist/docs/deployment.html +2 -2
  13. package/dist/docs/evals.html +2 -2
  14. package/dist/docs/example-agents/approval-buddy.html +2 -2
  15. package/dist/docs/example-agents/benny.html +2 -2
  16. package/dist/docs/example-agents/bugbot.html +2 -2
  17. package/dist/docs/example-agents/codebase-wiki.html +2 -2
  18. package/dist/docs/example-agents/codeowners-review.html +2 -2
  19. package/dist/docs/example-agents/concierge.html +2 -2
  20. package/dist/docs/example-agents/fsd.html +2 -2
  21. package/dist/docs/example-agents/index.html +2 -2
  22. package/dist/docs/example-agents/knowledge-base.html +2 -2
  23. package/dist/docs/example-agents/security-reviewer.html +2 -2
  24. package/dist/docs/example-agents/slack-agent.html +2 -2
  25. package/dist/docs/example-agents/weather-agent.html +2 -2
  26. package/dist/docs/guides/agent-to-agent.html +2 -2
  27. package/dist/docs/guides/cloud-runtime.html +2 -2
  28. package/dist/docs/guides/github.html +2 -2
  29. package/dist/docs/guides/human-in-the-loop.html +2 -2
  30. package/dist/docs/guides/slack.html +2 -2
  31. package/dist/docs/guides/webhooks.html +2 -2
  32. package/dist/docs/hillclimbing.html +2 -2
  33. package/dist/docs/index.html +2 -2
  34. package/dist/docs/quickstart.html +2 -2
  35. package/dist/docs/reference/agent-config.html +2 -2
  36. package/dist/docs/reference/channels.html +2 -2
  37. package/dist/docs/reference/cli.html +2 -2
  38. package/dist/docs/reference/connections.html +2 -2
  39. package/dist/docs/reference/hooks.html +2 -2
  40. package/dist/docs/reference/http-api.html +2 -2
  41. package/dist/docs/reference/instructions.html +2 -2
  42. package/dist/docs/reference/playground.html +2 -2
  43. package/dist/docs/reference/project-layout.html +2 -2
  44. package/dist/docs/reference/schedules.html +2 -2
  45. package/dist/docs/reference/sessions.html +2 -2
  46. package/dist/docs/reference/skills.html +2 -2
  47. package/dist/docs/reference/subagents.html +2 -2
  48. package/dist/docs/reference/tools.html +2 -2
  49. package/dist/docs/scaffolding-agents.html +2 -2
  50. package/dist/docs/storage.html +2 -2
  51. package/dist/docs/troubleshooting.html +2 -2
  52. package/dist/evals.d.ts +2 -2
  53. package/dist/internal/cli-ax.d.ts +23 -10
  54. package/dist/internal/cli-ax.d.ts.map +1 -1
  55. package/dist/internal/cli-ax.js +358 -55
  56. package/dist/internal/eval-run-store.d.ts +2 -0
  57. package/dist/internal/eval-run-store.d.ts.map +1 -1
  58. package/dist/internal/eval-run-store.js +4 -4
  59. package/dist/internal/evals-client.d.ts +96 -0
  60. package/dist/internal/evals-client.d.ts.map +1 -0
  61. package/dist/internal/evals-client.js +262 -0
  62. package/dist/internal/init-project.js +3 -3
  63. package/dist/internal/json-dir-store.d.ts +32 -0
  64. package/dist/internal/json-dir-store.d.ts.map +1 -0
  65. package/dist/internal/persistence-coordinator.d.ts +127 -0
  66. package/dist/internal/persistence-coordinator.d.ts.map +1 -0
  67. package/dist/internal/server.js +11 -2
  68. package/dist/persistence.d.ts +184 -0
  69. package/dist/persistence.d.ts.map +1 -0
  70. package/dist/playground/assets/index-72vCOBWO.js +86 -0
  71. package/dist/playground/assets/index-BjnMwYoR.css +1 -0
  72. package/dist/playground/index.html +2 -2
  73. package/package.json +1 -1
  74. package/src/bin/agent-serve.ts +47 -16
  75. package/src/evals.ts +2 -2
  76. package/src/internal/cli-ax.ts +466 -75
  77. package/src/internal/eval-run-store.ts +5 -0
  78. package/src/internal/evals-client.ts +431 -0
  79. package/src/internal/init-project.ts +3 -3
  80. package/src/internal/server.ts +12 -0
  81. package/dist/docs/assets/chunks/@localSearchIndexroot.O6eP7QZr.js +0 -1
  82. package/dist/playground/assets/index-7GJIUFQj.css +0 -1
  83. package/dist/playground/assets/index-mux9aj5P.js +0 -319
@@ -0,0 +1,431 @@
1
+ /**
2
+ * HTTP client for playground eval batches (`/v1/dev/evals/runs`).
3
+ *
4
+ * Used by `agentkit eval --prod` (or `--url`) so the batch runs on the serve
5
+ * process and is mirrored to `defineStorage` when an `evals` table is
6
+ * configured. Without those flags, eval keeps the in-process ephemeral path
7
+ * in `cli-ax.ts`.
8
+ */
9
+
10
+ import type { EvalRunSnapshot } from "../evals.js";
11
+ import { matchesEvalIdFilter } from "../evals.js";
12
+ import type { EvalRunResult } from "./eval-runner.js";
13
+ import { mergeRequestHeaders, type RequestAuth } from "./request-headers.js";
14
+
15
+ const DEFAULT_POLL_MS = 1_000;
16
+
17
+ export type RemoteEvalErrorCode =
18
+ | "no_matching_evals"
19
+ | "eval_run_in_progress"
20
+ | "unknown_eval_run"
21
+ | "start_failed"
22
+ | "poll_failed"
23
+ | "aborted";
24
+
25
+ export class RemoteEvalError extends Error {
26
+ constructor(
27
+ message: string,
28
+ readonly status?: number,
29
+ readonly code?: RemoteEvalErrorCode,
30
+ readonly activeRunId?: string
31
+ ) {
32
+ super(message);
33
+ this.name = "RemoteEvalError";
34
+ }
35
+ }
36
+
37
+ export interface RemoteEvalListItem {
38
+ id: string;
39
+ fileId: string;
40
+ description?: string;
41
+ tags?: string[];
42
+ }
43
+
44
+ export interface RunRemoteEvalsOptions extends RequestAuth {
45
+ baseUrl: string;
46
+ filterIds?: string[];
47
+ tags?: string[];
48
+ /** Per-case timeout override forwarded to the serve process. */
49
+ timeoutMs?: number;
50
+ /** Ask the serve harness to emit verbose case logs (server-side). */
51
+ verbose?: boolean;
52
+ fetchImpl?: typeof fetch;
53
+ /** Poll interval while the batch is `running`. */
54
+ pollMs?: number;
55
+ /** Abort the wait (not the remote batch). */
56
+ signal?: AbortSignal;
57
+ /** Called each time the polled snapshot changes. */
58
+ onSnapshot?: (snap: EvalRunSnapshot) => void;
59
+ }
60
+
61
+ /**
62
+ * Start a batch on the target serve via `POST /v1/dev/evals/runs`, poll until
63
+ * terminal, and return CLI-shaped case results.
64
+ */
65
+ export async function runRemoteEvals(
66
+ options: RunRemoteEvalsOptions
67
+ ): Promise<{ snapshot: EvalRunSnapshot; results: EvalRunResult[] }> {
68
+ const baseUrl = options.baseUrl.replace(/\/$/, "");
69
+ const fetchImpl = options.fetchImpl ?? fetch;
70
+ const auth: RequestAuth = {
71
+ bearerToken: options.bearerToken,
72
+ headers: options.headers,
73
+ };
74
+
75
+ const started = await startRemoteEvalRun({
76
+ baseUrl,
77
+ auth,
78
+ fetchImpl,
79
+ filterIds: options.filterIds,
80
+ tags: options.tags,
81
+ timeoutMs: options.timeoutMs,
82
+ verbose: options.verbose,
83
+ signal: options.signal,
84
+ });
85
+ options.onSnapshot?.(started);
86
+
87
+ const snapshot = await pollRemoteEvalRun({
88
+ baseUrl,
89
+ runId: started.runId,
90
+ auth,
91
+ fetchImpl,
92
+ pollMs: options.pollMs ?? DEFAULT_POLL_MS,
93
+ signal: options.signal,
94
+ onSnapshot: options.onSnapshot,
95
+ });
96
+
97
+ return { snapshot, results: snapshotToEvalRunResults(snapshot) };
98
+ }
99
+
100
+ /** List datapoints from `GET /v1/dev/evals` (same surface a remote run uses). */
101
+ export async function listRemoteEvals(args: {
102
+ baseUrl: string;
103
+ auth?: RequestAuth;
104
+ fetchImpl?: typeof fetch;
105
+ filterIds?: string[];
106
+ tags?: string[];
107
+ signal?: AbortSignal;
108
+ }): Promise<RemoteEvalListItem[]> {
109
+ const baseUrl = args.baseUrl.replace(/\/$/, "");
110
+ const fetchImpl = args.fetchImpl ?? fetch;
111
+ let resp: Response;
112
+ try {
113
+ resp = await fetchImpl(`${baseUrl}/v1/dev/evals`, {
114
+ headers: mergeRequestHeaders(args.auth),
115
+ signal: args.signal,
116
+ });
117
+ } catch (error) {
118
+ throw new RemoteEvalError(
119
+ error instanceof Error ? error.message : "Could not list evals.",
120
+ undefined,
121
+ "start_failed"
122
+ );
123
+ }
124
+ if (!resp.ok) {
125
+ throw new RemoteEvalError(
126
+ `Could not list evals (HTTP ${resp.status}).`,
127
+ resp.status,
128
+ "start_failed"
129
+ );
130
+ }
131
+ const body = (await resp.json()) as {
132
+ evals?: Array<{
133
+ id?: unknown;
134
+ fileId?: unknown;
135
+ description?: unknown;
136
+ tags?: unknown;
137
+ }>;
138
+ };
139
+ const listed: RemoteEvalListItem[] = [];
140
+ for (const row of body.evals ?? []) {
141
+ if (typeof row.id !== "string" || row.id === "") {
142
+ continue;
143
+ }
144
+ if (typeof row.fileId !== "string" || row.fileId === "") {
145
+ continue;
146
+ }
147
+ const tags = Array.isArray(row.tags)
148
+ ? row.tags.filter((t): t is string => typeof t === "string")
149
+ : undefined;
150
+ listed.push({
151
+ id: row.id,
152
+ fileId: row.fileId,
153
+ ...(typeof row.description === "string"
154
+ ? { description: row.description }
155
+ : {}),
156
+ ...(tags === undefined || tags.length === 0 ? {} : { tags }),
157
+ });
158
+ }
159
+
160
+ let selected = listed;
161
+ if (args.filterIds !== undefined && args.filterIds.length > 0) {
162
+ selected = selected.filter((e) =>
163
+ args.filterIds!.some((f) => matchesEvalIdFilter(e.id, f))
164
+ );
165
+ }
166
+ if (args.tags !== undefined && args.tags.length > 0) {
167
+ selected = selected.filter((e) =>
168
+ (e.tags ?? []).some((t) => args.tags!.includes(t))
169
+ );
170
+ }
171
+ return selected;
172
+ }
173
+
174
+ export async function startRemoteEvalRun(args: {
175
+ baseUrl: string;
176
+ auth?: RequestAuth;
177
+ fetchImpl?: typeof fetch;
178
+ filterIds?: string[];
179
+ tags?: string[];
180
+ timeoutMs?: number;
181
+ verbose?: boolean;
182
+ signal?: AbortSignal;
183
+ }): Promise<EvalRunSnapshot> {
184
+ const baseUrl = args.baseUrl.replace(/\/$/, "");
185
+ const fetchImpl = args.fetchImpl ?? fetch;
186
+ const body: {
187
+ filterIds?: string[];
188
+ tags?: string[];
189
+ timeoutMs?: number;
190
+ verbose?: boolean;
191
+ } = {};
192
+ if (args.filterIds !== undefined && args.filterIds.length > 0) {
193
+ body.filterIds = args.filterIds;
194
+ }
195
+ if (args.tags !== undefined && args.tags.length > 0) {
196
+ body.tags = args.tags;
197
+ }
198
+ if (args.timeoutMs !== undefined) {
199
+ body.timeoutMs = args.timeoutMs;
200
+ }
201
+ if (args.verbose === true) {
202
+ body.verbose = true;
203
+ }
204
+
205
+ let resp: Response;
206
+ try {
207
+ resp = await fetchImpl(`${baseUrl}/v1/dev/evals/runs`, {
208
+ method: "POST",
209
+ headers: {
210
+ "content-type": "application/json",
211
+ ...mergeRequestHeaders(args.auth),
212
+ },
213
+ body: JSON.stringify(body),
214
+ signal: args.signal,
215
+ });
216
+ } catch (error) {
217
+ throw new RemoteEvalError(
218
+ error instanceof Error ? error.message : "Could not start eval run.",
219
+ undefined,
220
+ "start_failed"
221
+ );
222
+ }
223
+
224
+ const json = (await resp.json().catch(() => ({}))) as {
225
+ ok?: boolean;
226
+ error?: string;
227
+ message?: string;
228
+ activeRunId?: string;
229
+ runId?: string;
230
+ };
231
+
232
+ if (json.error === "no_matching_evals") {
233
+ throw new RemoteEvalError(
234
+ "No matching evals found.",
235
+ 404,
236
+ "no_matching_evals"
237
+ );
238
+ }
239
+ if (resp.status === 409 || json.error === "eval_run_in_progress") {
240
+ throw new RemoteEvalError(
241
+ json.activeRunId !== undefined && json.activeRunId !== ""
242
+ ? `An eval run is already in progress (${json.activeRunId}).`
243
+ : "An eval run is already in progress.",
244
+ 409,
245
+ "eval_run_in_progress",
246
+ typeof json.activeRunId === "string" ? json.activeRunId : undefined
247
+ );
248
+ }
249
+ if (!resp.ok) {
250
+ const detail =
251
+ typeof json.message === "string" && json.message !== ""
252
+ ? json.message
253
+ : typeof json.error === "string" && json.error !== ""
254
+ ? json.error
255
+ : `HTTP ${resp.status}`;
256
+ throw new RemoteEvalError(
257
+ `Could not start eval run: ${detail}`,
258
+ resp.status,
259
+ "start_failed"
260
+ );
261
+ }
262
+ if (typeof json.runId !== "string" || json.runId === "") {
263
+ throw new RemoteEvalError(
264
+ "Eval start response missing runId.",
265
+ undefined,
266
+ "start_failed"
267
+ );
268
+ }
269
+ return json as EvalRunSnapshot;
270
+ }
271
+
272
+ /** One-shot `GET /v1/dev/evals/runs/:runId` (no polling). */
273
+ export async function getRemoteEvalRun(args: {
274
+ baseUrl: string;
275
+ runId: string;
276
+ auth?: RequestAuth;
277
+ fetchImpl?: typeof fetch;
278
+ signal?: AbortSignal;
279
+ }): Promise<EvalRunSnapshot> {
280
+ const baseUrl = args.baseUrl.replace(/\/$/, "");
281
+ const fetchImpl = args.fetchImpl ?? fetch;
282
+ let resp: Response;
283
+ try {
284
+ resp = await fetchImpl(
285
+ `${baseUrl}/v1/dev/evals/runs/${encodeURIComponent(args.runId)}`,
286
+ {
287
+ headers: mergeRequestHeaders(args.auth),
288
+ signal: args.signal,
289
+ }
290
+ );
291
+ } catch (error) {
292
+ throw new RemoteEvalError(
293
+ error instanceof Error ? error.message : "Could not fetch eval run.",
294
+ undefined,
295
+ "poll_failed"
296
+ );
297
+ }
298
+
299
+ if (resp.status === 404) {
300
+ throw new RemoteEvalError(
301
+ `Unknown eval run ${args.runId}.`,
302
+ 404,
303
+ "unknown_eval_run"
304
+ );
305
+ }
306
+ if (!resp.ok) {
307
+ throw new RemoteEvalError(
308
+ `Could not fetch eval run (HTTP ${resp.status}).`,
309
+ resp.status,
310
+ "poll_failed"
311
+ );
312
+ }
313
+ return (await resp.json()) as EvalRunSnapshot;
314
+ }
315
+
316
+ /** Recent batches from `GET /v1/dev/evals/runs`. */
317
+ export async function listRemoteEvalRuns(args: {
318
+ baseUrl: string;
319
+ auth?: RequestAuth;
320
+ fetchImpl?: typeof fetch;
321
+ signal?: AbortSignal;
322
+ }): Promise<{ runs: EvalRunSnapshot[]; activeRunId?: string }> {
323
+ const baseUrl = args.baseUrl.replace(/\/$/, "");
324
+ const fetchImpl = args.fetchImpl ?? fetch;
325
+ let resp: Response;
326
+ try {
327
+ resp = await fetchImpl(`${baseUrl}/v1/dev/evals/runs`, {
328
+ headers: mergeRequestHeaders(args.auth),
329
+ signal: args.signal,
330
+ });
331
+ } catch (error) {
332
+ throw new RemoteEvalError(
333
+ error instanceof Error ? error.message : "Could not list eval runs.",
334
+ undefined,
335
+ "poll_failed"
336
+ );
337
+ }
338
+ if (!resp.ok) {
339
+ throw new RemoteEvalError(
340
+ `Could not list eval runs (HTTP ${resp.status}).`,
341
+ resp.status,
342
+ "poll_failed"
343
+ );
344
+ }
345
+ const body = (await resp.json()) as {
346
+ runs?: unknown;
347
+ activeRunId?: unknown;
348
+ };
349
+ const runs = Array.isArray(body.runs)
350
+ ? (body.runs.filter(
351
+ (r): r is EvalRunSnapshot =>
352
+ r !== null &&
353
+ typeof r === "object" &&
354
+ typeof (r as EvalRunSnapshot).runId === "string"
355
+ ) as EvalRunSnapshot[])
356
+ : [];
357
+ return {
358
+ runs,
359
+ ...(typeof body.activeRunId === "string" && body.activeRunId !== ""
360
+ ? { activeRunId: body.activeRunId }
361
+ : {}),
362
+ };
363
+ }
364
+
365
+ export async function pollRemoteEvalRun(args: {
366
+ baseUrl: string;
367
+ runId: string;
368
+ auth?: RequestAuth;
369
+ fetchImpl?: typeof fetch;
370
+ pollMs?: number;
371
+ signal?: AbortSignal;
372
+ onSnapshot?: (snap: EvalRunSnapshot) => void;
373
+ }): Promise<EvalRunSnapshot> {
374
+ const pollMs = args.pollMs ?? DEFAULT_POLL_MS;
375
+ let lastJson = "";
376
+
377
+ for (;;) {
378
+ if (args.signal?.aborted === true) {
379
+ throw new RemoteEvalError("Eval wait aborted.", undefined, "aborted");
380
+ }
381
+
382
+ const snap = await getRemoteEvalRun(args);
383
+ const serialized = JSON.stringify(snap);
384
+ if (serialized !== lastJson) {
385
+ lastJson = serialized;
386
+ args.onSnapshot?.(snap);
387
+ }
388
+ if (snap.status !== "running") {
389
+ return snap;
390
+ }
391
+ await sleep(pollMs, args.signal);
392
+ }
393
+ }
394
+
395
+ export function snapshotToEvalRunResults(
396
+ snapshot: EvalRunSnapshot
397
+ ): EvalRunResult[] {
398
+ return snapshot.cases.map((c) => ({
399
+ id: c.id,
400
+ path: c.fileId,
401
+ ...(c.description === undefined ? {} : { description: c.description }),
402
+ ok: c.ok === true,
403
+ assertions: c.assertions ?? [],
404
+ ...(c.sessionId === undefined ? {} : { sessionId: c.sessionId }),
405
+ inputs: c.inputs ?? [],
406
+ ...(c.finalText === undefined ? {} : { finalText: c.finalText }),
407
+ ...(c.tools === undefined ? {} : { tools: c.tools }),
408
+ ...(c.toolCalls === undefined ? {} : { toolCalls: c.toolCalls }),
409
+ ...(c.error === undefined ? {} : { error: c.error }),
410
+ logs: c.logs ?? [],
411
+ durationMs: c.durationMs ?? 0,
412
+ }));
413
+ }
414
+
415
+ function sleep(ms: number, signal?: AbortSignal): Promise<void> {
416
+ return new Promise((resolve, reject) => {
417
+ if (signal?.aborted === true) {
418
+ reject(new RemoteEvalError("Eval wait aborted.", undefined, "aborted"));
419
+ return;
420
+ }
421
+ const timer = setTimeout(() => {
422
+ signal?.removeEventListener("abort", onAbort);
423
+ resolve();
424
+ }, ms);
425
+ const onAbort = (): void => {
426
+ clearTimeout(timer);
427
+ reject(new RemoteEvalError("Eval wait aborted.", undefined, "aborted"));
428
+ };
429
+ signal?.addEventListener("abort", onAbort, { once: true });
430
+ });
431
+ }
@@ -433,15 +433,15 @@ Recommended loop once the project exists:
433
433
  1. Edit agent/ + tools under agent/tools/
434
434
  2. ${CLI_COMMAND_NAME} info --dir . --json
435
435
  3. ${CLI_COMMAND_NAME} run --dir . --message "..."
436
- 4. ${CLI_COMMAND_NAME} eval --local --dir .
436
+ 4. ${CLI_COMMAND_NAME} eval --dir .
437
437
 
438
438
  Evals:
439
439
  Create evals/**/*.eval.ts using defineEval from ${PACKAGE_NAME}/evals
440
440
  Case id is the path: evals/weather/nyc.eval.ts → weather/nyc
441
441
  Required: evals/evals.config.ts with defineEvalConfig({ maxConcurrency })
442
442
  (hard-capped at 200 due to model provider request limits)
443
- Run: ${CLI_COMMAND_NAME} eval --local [--json] [evalId...]
444
- (omit --local to run against the hosted deployment)
443
+ Run: ${CLI_COMMAND_NAME} eval [--json] [evalId...]
444
+ (pass --prod to run against the hosted deployment)
445
445
 
446
446
  See AGENTS.md in the ${PACKAGE_NAME} package for the full loop.
447
447
  `);
@@ -1082,6 +1082,8 @@ function mountAgentRoutes(
1082
1082
  const body = await readJsonObject(request);
1083
1083
  const filterIds = asStringArray(body?.filterIds);
1084
1084
  const tags = asStringArray(body?.tags);
1085
+ const timeoutMs = asPositiveInt(body?.timeoutMs);
1086
+ const verbose = body?.verbose === true;
1085
1087
  const authHeader = request.headers.get("authorization");
1086
1088
  const bearerToken = authHeader?.toLowerCase().startsWith("bearer ")
1087
1089
  ? authHeader.slice("bearer ".length).trim()
@@ -1093,6 +1095,8 @@ function mountAgentRoutes(
1093
1095
  ...(bearerToken === undefined || bearerToken === ""
1094
1096
  ? {}
1095
1097
  : { bearerToken }),
1098
+ ...(timeoutMs === undefined ? {} : { timeoutMs }),
1099
+ ...(verbose ? { verbose: true } : {}),
1096
1100
  });
1097
1101
  return jsonResponse(snap, { status: 202 });
1098
1102
  } catch (error) {
@@ -1896,6 +1900,14 @@ function asStringArray(value: unknown): string[] | undefined {
1896
1900
  return out.length === 0 ? undefined : out;
1897
1901
  }
1898
1902
 
1903
+ function asPositiveInt(value: unknown): number | undefined {
1904
+ if (typeof value !== "number" || !Number.isFinite(value)) {
1905
+ return undefined;
1906
+ }
1907
+ const n = Math.floor(value);
1908
+ return n > 0 ? n : undefined;
1909
+ }
1910
+
1899
1911
  async function assertReminderReadable(input: {
1900
1912
  engine: SessionEngine;
1901
1913
  reminder: ReminderInfo;