@reticlehq/server 3.8.0 → 3.8.1

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 (106) hide show
  1. package/SKILL.md +2 -2
  2. package/dist/command/cli/answers/gate-hook-message.d.ts +2 -0
  3. package/dist/command/cli/answers/gate-hook-message.js +5 -1
  4. package/dist/command/cli/auth/cloud-session.d.ts +1 -19
  5. package/dist/command/cli/auth/device-flow.d.ts +3 -37
  6. package/dist/command/cli/cli-doctor.js +30 -23
  7. package/dist/command/cli/cli-flow-commands.js +7 -1
  8. package/dist/command/cli/cli-parse-serve.d.ts +24 -0
  9. package/dist/command/cli/cli-parse-serve.js +124 -0
  10. package/dist/command/cli/cli-parse.d.ts +1 -10
  11. package/dist/command/cli/cli-parse.js +62 -121
  12. package/dist/command/cli/cli-usage.js +5 -2
  13. package/dist/command/cli/cloud-link.d.ts +1 -20
  14. package/dist/command/cli/doctor/doctor-agent-link.d.ts +23 -0
  15. package/dist/command/cli/doctor/doctor-agent-link.js +28 -0
  16. package/dist/command/cli/ports/port-holder.js +6 -1
  17. package/dist/command/cli/project-name.d.ts +1 -7
  18. package/dist/command/cli/project-name.js +1 -0
  19. package/dist/command/daemon/daemon.d.ts +6 -0
  20. package/dist/command/daemon/daemon.js +52 -2
  21. package/dist/command/daemon/win32-escape.d.ts +32 -0
  22. package/dist/command/daemon/win32-escape.js +119 -0
  23. package/dist/command/setup/bringup/dev-server-files.d.ts +1 -9
  24. package/dist/command/setup/bringup/dev-server-wait.js +5 -1
  25. package/dist/command/version/version-skew.d.ts +2 -0
  26. package/dist/command/version/version-skew.js +7 -3
  27. package/dist/features/crawl/crawl-tools.js +2 -2
  28. package/dist/features/exhaust/ledger.d.ts +15 -96
  29. package/dist/features/exhaust/ledger.js +3 -3
  30. package/dist/features/license/license.d.ts +2 -14
  31. package/dist/hooks/hook-commands.js +19 -9
  32. package/dist/judgement/capsule/capsule-store.d.ts +2 -39
  33. package/dist/language/flows/annotate-tools.js +1 -1
  34. package/dist/language/flows/fields/flow-secret-field.js +1 -1
  35. package/dist/language/flows/flow-expect-grammar.js +14 -6
  36. package/dist/language/flows/flow-lease.d.ts +27 -0
  37. package/dist/language/flows/flow-lease.js +59 -0
  38. package/dist/language/flows/flow-replay-run.d.ts +1 -87
  39. package/dist/language/flows/flow-replay-run.js +88 -283
  40. package/dist/language/flows/flow-replayable.d.ts +1 -1
  41. package/dist/language/flows/flow-start-path.d.ts +108 -0
  42. package/dist/language/flows/flow-start-path.js +313 -0
  43. package/dist/language/flows/flow-tools.d.ts +2 -23
  44. package/dist/language/flows/flow-tools.js +34 -66
  45. package/dist/language/flows/recording/flake.d.ts +3 -27
  46. package/dist/language/flows/recording/flake.js +1 -1
  47. package/dist/language/flows/stores/assertion-tiers-store.js +1 -1
  48. package/dist/machine/git-tracks.js +1 -0
  49. package/dist/memory/cloud/cloud-sync.d.ts +2 -24
  50. package/dist/memory/intent/intent-shard.d.ts +41 -266
  51. package/dist/memory/journal/envelope-store.js +1 -1
  52. package/dist/memory/journal/envelope.d.ts +6 -154
  53. package/dist/memory/journal/on-disk/ambient-file.d.ts +1 -7
  54. package/dist/memory/journal/on-disk/ambient-file.js +1 -1
  55. package/dist/memory/journal/self-observation.js +3 -1
  56. package/dist/memory/journal/session-journal.js +39 -20
  57. package/dist/portal/input/viewport-tools.d.ts +19 -15
  58. package/dist/portal/pool/browser-pool.d.ts +5 -55
  59. package/dist/portal/pool/browser-pool.js +121 -100
  60. package/dist/portal/pool/pool-lease.d.ts +128 -0
  61. package/dist/portal/pool/pool-lease.js +49 -0
  62. package/dist/portal/production/production-activation.d.ts +5 -27
  63. package/dist/portal/session/control-envelope.d.ts +1 -1
  64. package/dist/portal/session/zero-install-first-call.d.ts +3 -0
  65. package/dist/portal/session/zero-install-first-call.js +33 -12
  66. package/dist/surface/mcp/mcp.d.ts +3 -3
  67. package/dist/surface/mcp/mcp.js +4 -6
  68. package/dist/surface/tools/act/act-danger.js +32 -17
  69. package/dist/surface/tools/act/durable.js +1 -1
  70. package/dist/surface/tools/act-sequence-tool.js +3 -3
  71. package/dist/surface/tools/act-tools.js +13 -14
  72. package/dist/surface/tools/agent/compact-envelope.d.ts +2 -2
  73. package/dist/surface/tools/coverage-tools.js +1 -1
  74. package/dist/surface/tools/dynamic-tools.d.ts +8 -0
  75. package/dist/surface/tools/dynamic-tools.js +36 -16
  76. package/dist/surface/tools/empty-session-list.d.ts +5 -29
  77. package/dist/surface/tools/feedback-tools.js +1 -1
  78. package/dist/surface/tools/gap-novelty.d.ts +1 -1
  79. package/dist/surface/tools/gaps/capability-absences.js +4 -3
  80. package/dist/surface/tools/harness-gate.d.ts +1 -1
  81. package/dist/surface/tools/harness-toolset.js +3 -4
  82. package/dist/surface/tools/lease-permissions.d.ts +1 -1
  83. package/dist/surface/tools/lease-readiness.d.ts +23 -1
  84. package/dist/surface/tools/lease-readiness.js +75 -9
  85. package/dist/surface/tools/lease-tools.d.ts +1 -52
  86. package/dist/surface/tools/lease-tools.js +79 -142
  87. package/dist/surface/tools/observe-tools.js +3 -1
  88. package/dist/surface/tools/read-tools.js +5 -5
  89. package/dist/surface/tools/session-ports.d.ts +57 -0
  90. package/dist/surface/tools/session-ports.js +102 -0
  91. package/dist/surface/tools/tool-kit.d.ts +5 -3
  92. package/dist/surface/tools/tools.js +1 -1
  93. package/dist/telemetry/daemon-telemetry.d.ts +3 -1
  94. package/dist/telemetry/daemon-telemetry.js +9 -2
  95. package/dist/telemetry/telemetry.d.ts +3 -1
  96. package/dist/telemetry/telemetry.js +2 -0
  97. package/docs/agent-cheatsheet.md +12 -0
  98. package/docs/cli/gate.mdx +1 -1
  99. package/docs/cli/verify.mdx +8 -1
  100. package/docs/first-drive.md +7 -0
  101. package/docs/multi-agent-testing.md +2 -0
  102. package/docs/predicates.mdx +6 -0
  103. package/docs/skill.md +3 -3
  104. package/docs/troubleshooting.mdx +10 -0
  105. package/docs/usage.md +1 -1
  106. package/package.json +8 -9
@@ -46,6 +46,7 @@ const NET_TYPES = new Set([
46
46
  EventType.NET_REQUEST,
47
47
  EventType.NET_PENDING,
48
48
  EventType.NET_DETAIL,
49
+ EventType.NET_RESPONSE_BODY,
49
50
  EventType.NET_STREAM,
50
51
  ]);
51
52
  /** A base for parsing relative URLs. Never dereferenced — only the path is read. */
@@ -60,8 +61,9 @@ function pathOf(url) {
60
61
  }
61
62
  /** True when this event is the page fetching Reticle's own code rather than doing anything. */
62
63
  export function isSelfObservation(event) {
63
- if (!NET_TYPES.has(event.type))
64
+ if (!NET_TYPES.has(event.type)) {
64
65
  return false;
66
+ }
65
67
  const path = pathOf(urlForMatch(event.data));
66
68
  return path.includes(SCOPE_PATH) || path.includes(SCOPE_FLATTENED);
67
69
  }
@@ -49,8 +49,9 @@ const CACHE_COMPACT_AT = 1024;
49
49
  * bound is used in and silently disable it, which is the one failure mode a bound must not have.
50
50
  */
51
51
  function boundedOption(value, fallback, name) {
52
- if (value === undefined)
52
+ if (value === undefined) {
53
53
  return fallback;
54
+ }
54
55
  if (!Number.isSafeInteger(value) || value <= 0) {
55
56
  throw new Error(`refusing a journal ${name} that is not a positive whole number: ${String(value)}`);
56
57
  }
@@ -184,11 +185,13 @@ export class SessionJournal {
184
185
  * already on disk is deleted, and the loss is at the end where it can be declared.
185
186
  */
186
187
  async appendEvents(events) {
187
- if (0 === events.length)
188
+ if (0 === events.length) {
188
189
  return;
190
+ }
189
191
  // Durable, not a field: see #writeLoss. A closed ledger is closed for a one-event batch too.
190
- if ((await this.#closureReport()) !== undefined)
192
+ if ((await this.#closureReport()) !== undefined) {
191
193
  return;
194
+ }
192
195
  await this.#ensureDir();
193
196
  const path = journalEventsPath(this.#root, this.#sessionId);
194
197
  const written = await this.#eventBytesOnDisk(path);
@@ -221,8 +224,9 @@ export class SessionJournal {
221
224
  */
222
225
  async #eventBytesOnDisk(path) {
223
226
  const known = this.#eventBytes;
224
- if (known !== undefined)
227
+ if (known !== undefined) {
225
228
  return known;
229
+ }
226
230
  let size;
227
231
  try {
228
232
  size = (await this.#fs.stat(path)).size;
@@ -235,8 +239,9 @@ export class SessionJournal {
235
239
  // by exactly the conditions most likely to accompany a failing disk. Thrown before anything is
236
240
  // appended; `JournalRecorder` swallows sink errors, so a live session is never taken down by
237
241
  // one.
238
- if (!this.#fs.isNotFound(error))
242
+ if (!this.#fs.isNotFound(error)) {
239
243
  throw error;
244
+ }
240
245
  size = 0;
241
246
  }
242
247
  this.#eventBytes = size;
@@ -252,16 +257,18 @@ export class SessionJournal {
252
257
  * necessarily the ceiling that closed the file.
253
258
  */
254
259
  async #closureReport() {
255
- if (this.#lossLoaded)
260
+ if (this.#lossLoaded) {
256
261
  return this.#writeLoss;
262
+ }
257
263
  let text;
258
264
  try {
259
265
  text = await this.#fs.readFile(journalClosedPath(this.#root, this.#sessionId));
260
266
  }
261
267
  catch (error) {
262
268
  // Same rule as the size read above, and for the same reason: only ENOENT is "never closed".
263
- if (!this.#fs.isNotFound(error))
269
+ if (!this.#fs.isNotFound(error)) {
264
270
  throw error;
271
+ }
265
272
  this.#lossLoaded = true;
266
273
  return undefined;
267
274
  }
@@ -371,8 +378,9 @@ export class SessionJournal {
371
378
  await this.#fs.appendFile(path, text);
372
379
  }
373
380
  catch (error) {
374
- if (!this.#fs.isNotFound(error))
381
+ if (!this.#fs.isNotFound(error)) {
375
382
  throw error;
383
+ }
376
384
  this.#dirEnsured = false;
377
385
  await this.#ensureDir();
378
386
  await this.#fs.appendFile(path, text);
@@ -390,8 +398,9 @@ export class SessionJournal {
390
398
  chunk = await readFrom(path, this.#eventBytesConsumed, this.#maxReadBytes);
391
399
  }
392
400
  catch (error) {
393
- if (this.#fs.isNotFound(error))
401
+ if (this.#fs.isNotFound(error)) {
394
402
  return this.#liveEvents();
403
+ }
395
404
  throw error;
396
405
  }
397
406
  // Shrink guard: if the file is smaller than what we consumed, the offset is meaningless — reset
@@ -408,8 +417,9 @@ export class SessionJournal {
408
417
  const from = chunk.from ?? this.#eventBytesConsumed;
409
418
  const skipped = Math.max(0, from - this.#eventBytesConsumed);
410
419
  this.#ingestTail(chunk.text);
411
- if (skipped > 0)
420
+ if (skipped > 0) {
412
421
  this.#noteCut(skipped);
422
+ }
413
423
  // Resume at the end of the last COMPLETE line, counted forward over the bytes that actually
414
424
  // ARRIVED. Not from `chunk.size`: that is the file's end as `fstat` reported it, and a read
415
425
  // truncated or cut short returns less — a cursor there would skip every unread byte, silently
@@ -424,8 +434,9 @@ export class SessionJournal {
424
434
  text = await this.#fs.readFile(path);
425
435
  }
426
436
  catch (error) {
427
- if (this.#fs.isNotFound(error))
437
+ if (this.#fs.isNotFound(error)) {
428
438
  return this.#liveEvents();
439
+ }
429
440
  throw error;
430
441
  }
431
442
  // Fallback (whole-file) path: #eventCharsConsumed is a UTF-16 offset into the whole text. Shrink
@@ -467,11 +478,13 @@ export class SessionJournal {
467
478
  */
468
479
  #ingestTail(tail) {
469
480
  const end = tail.lastIndexOf('\n') + 1;
470
- if (0 === end)
471
- return; // no complete line yet
481
+ if (0 === end) {
482
+ return;
483
+ } // no complete line yet
472
484
  for (const line of tail.slice(0, end).split('\n')) {
473
- if (0 === line.length)
485
+ if (0 === line.length) {
474
486
  continue;
487
+ }
475
488
  let parsed;
476
489
  try {
477
490
  parsed = JSON.parse(line);
@@ -515,8 +528,9 @@ export class SessionJournal {
515
528
  this.#droppedBytes += cost;
516
529
  this.#droppedEvents += 1;
517
530
  // Exact, unlike a byte cut: this record was parsed, so the millisecond it was lost at is known.
518
- if (victim !== undefined)
531
+ if (victim !== undefined) {
519
532
  this.#lostThroughT = Math.max(this.#lostThroughT, victim.t);
533
+ }
520
534
  // Release the payload now. Waiting for slot compaction keeps evicted snapshots strongly
521
535
  // reachable, so a short returned list alone would not bound retained evidence in memory.
522
536
  this.#eventCache[this.#cacheHead] = undefined;
@@ -545,8 +559,9 @@ export class SessionJournal {
545
559
  * is what carries this to a verdict.
546
560
  */
547
561
  readLoss() {
548
- if (0 === this.#droppedBytes && 0 === this.#droppedEvents)
562
+ if (0 === this.#droppedBytes && 0 === this.#droppedEvents) {
549
563
  return undefined;
564
+ }
550
565
  const bounded = Number.isFinite(this.#lostThroughT);
551
566
  return {
552
567
  droppedBytes: this.#droppedBytes,
@@ -568,8 +583,9 @@ export class SessionJournal {
568
583
  return this.#readLines(journalActionsPath(this.#root, this.#sessionId), JournalActionSchema);
569
584
  }
570
585
  async #ensureDir() {
571
- if (this.#dirEnsured)
586
+ if (this.#dirEnsured) {
572
587
  return;
588
+ }
573
589
  await this.#fs.mkdir(sessionDirPath(this.#root, this.#sessionId));
574
590
  this.#dirEnsured = true;
575
591
  }
@@ -579,14 +595,16 @@ export class SessionJournal {
579
595
  text = await this.#fs.readFile(path);
580
596
  }
581
597
  catch (error) {
582
- if (this.#fs.isNotFound(error))
598
+ if (this.#fs.isNotFound(error)) {
583
599
  return [];
600
+ }
584
601
  throw error;
585
602
  }
586
603
  const out = [];
587
604
  for (const line of text.split('\n')) {
588
- if (0 === line.length)
605
+ if (0 === line.length) {
589
606
  continue;
607
+ }
590
608
  let parsed;
591
609
  try {
592
610
  parsed = JSON.parse(line);
@@ -596,8 +614,9 @@ export class SessionJournal {
596
614
  }
597
615
  const result = schema.safeParse(parsed);
598
616
  // Validated at this boundary; ZodTypeAny widens `.data` to any, so re-narrow to the schema output.
599
- if (result.success)
617
+ if (result.success) {
600
618
  out.push(result.data);
619
+ }
601
620
  }
602
621
  return out;
603
622
  }
@@ -1,24 +1,28 @@
1
1
  import { z } from 'zod';
2
2
  import type { ToolDef } from '../../surface/tools/tool-kit.js';
3
3
  import { type LeaseEmulation, type LeaseMedia } from '../pool/lease-emulation.js';
4
- export declare const COLOR_SCHEME_ARG: z.ZodOptional<z.ZodEnum<["light", "dark", "no-preference"]>>;
5
- export declare const REDUCED_MOTION_ARG: z.ZodOptional<z.ZodEnum<["reduce", "no-preference"]>>;
4
+ export declare const COLOR_SCHEME_ARG: z.ZodOptional<z.ZodEnum<{
5
+ light: "light";
6
+ dark: "dark";
7
+ "no-preference": "no-preference";
8
+ }>>;
9
+ export declare const REDUCED_MOTION_ARG: z.ZodOptional<z.ZodEnum<{
10
+ reduce: "reduce";
11
+ "no-preference": "no-preference";
12
+ }>>;
6
13
  export declare const LEASE_EMULATE_ARG: z.ZodOptional<z.ZodObject<{
7
- colorScheme: z.ZodOptional<z.ZodEnum<["light", "dark", "no-preference"]>>;
8
- reducedMotion: z.ZodOptional<z.ZodEnum<["reduce", "no-preference"]>>;
14
+ colorScheme: z.ZodOptional<z.ZodEnum<{
15
+ light: "light";
16
+ dark: "dark";
17
+ "no-preference": "no-preference";
18
+ }>>;
19
+ reducedMotion: z.ZodOptional<z.ZodEnum<{
20
+ reduce: "reduce";
21
+ "no-preference": "no-preference";
22
+ }>>;
9
23
  width: z.ZodOptional<z.ZodNumber>;
10
24
  height: z.ZodOptional<z.ZodNumber>;
11
- }, "strict", z.ZodTypeAny, {
12
- width?: number | undefined;
13
- height?: number | undefined;
14
- colorScheme?: "light" | "dark" | "no-preference" | undefined;
15
- reducedMotion?: "reduce" | "no-preference" | undefined;
16
- }, {
17
- width?: number | undefined;
18
- height?: number | undefined;
19
- colorScheme?: "light" | "dark" | "no-preference" | undefined;
20
- reducedMotion?: "reduce" | "no-preference" | undefined;
21
- }>>;
25
+ }, z.core.$strict>>;
22
26
  /** The media part of what a caller asked for, or undefined when it asked for none. */
23
27
  export declare function mediaOf(raw: {
24
28
  colorScheme?: unknown;
@@ -14,61 +14,9 @@ import type { Launcher, PooledMockRule, ScreenshotOptions } from './pool-contrac
14
14
  import type { RealInputArgs, RealInputResult } from '../input/real-input.js';
15
15
  import { type LeaseEmulation, type LeaseMedia } from './lease-emulation.js';
16
16
  export type { InitScriptHandle, Launcher, PooledBrowser, PooledContext, PooledCookie, PooledDialog, PooledMockRule, PooledPage, } from './pool-contract.js';
17
- /** A leased context+page for one flow. `release` frees the slot and closes the context. */
18
- export interface Lease {
19
- readonly sessionId: string;
20
- readonly url: string;
21
- readonly navStatus?: number;
22
- release(): Promise<void>;
23
- /**
24
- * Hand the page the zero-install reader: evaluated in the live document, and added ahead of every
25
- * document this lease loads next. False when there is no reader, or the page cannot run one.
26
- * Rejects with `NotHtmlDocumentError` when the page is not an HTML document.
27
- */
28
- injectReader?(): Promise<boolean>;
29
- /** Whether the page's document has not reached `complete` yet. False when it cannot be read. */
30
- isLoading?(): Promise<boolean>;
31
- }
32
- /** Default lease time-to-live: a lease untouched for this long is presumed orphaned and reclaimed. */
33
- export declare const DEFAULT_LEASE_TTL_MS: number;
34
- /**
35
- * How long a lease outlives the MCP connection that took it. Long enough for an agent whose
36
- * connection only blipped to touch it again; short enough that a gone agent's window closes soon.
37
- */
38
- export declare const ORPHANED_LEASE_GRACE_MS = 30000;
39
- interface BrowserPoolOptions {
40
- /** Max simultaneous leased contexts. Over-cap acquires queue. */
41
- maxContexts: number;
42
- /** Stable id generator for lease sessionIds (injected to stay clock-/random-free in logic). */
43
- genSessionId: () => string;
44
- /** Injected clock (ms). Defaults to Date.now; tests pass a controllable one. */
45
- now?: () => number;
46
- /** A lease untouched for longer than this is reclaimed by sweepExpired. */
47
- leaseTtlMs?: number;
48
- /** Per-lease navigation timeout — a page that won't load fails its own lease, never blocks a slot. */
49
- navTimeoutMs?: number;
50
- /**
51
- * The zero-install reader, built where the daemon knows its own port and pairing token. A thunk so
52
- * the ~450KB build is only read if some page ever needs it; undefined when there is none to give.
53
- */
54
- zeroInstallScript?: () => string | undefined;
55
- /**
56
- * Launches a browser with a window, for a lease somebody wants to watch. Launched only when the
57
- * first headed lease is asked for. Absent: a headed lease is refused.
58
- */
59
- launchHeaded?: Launcher;
60
- }
61
- /** What a headed lease is refused with where no window can be opened. */
62
- export declare const NO_HEADED_BROWSER = "no headed browser can be opened here";
63
- /**
64
- * An acquire that asked to be refused rather than queued found every slot taken (#1535). Queued,
65
- * it waited behind the cap with no deadline and no status, and `reticle drive` reported the 60 s
66
- * timeout as a daemon shutting down. The message names the cap and the one override for it.
67
- */
68
- export declare class PoolAtCapacityError extends Error {
69
- readonly max: number;
70
- constructor(max: number);
71
- }
17
+ import { type BrowserPoolOptions, type Lease } from './pool-lease.js';
18
+ export { DEFAULT_LEASE_TTL_MS, NO_HEADED_BROWSER, ORPHANED_LEASE_GRACE_MS, PoolAtCapacityError, } from './pool-lease.js';
19
+ export type { Lease } from './pool-lease.js';
72
20
  /**
73
21
  * Owns a headless browser (and, when asked, a headed one) and leases isolated contexts out of them,
74
22
  * capped at `maxContexts` across both.
@@ -171,6 +119,8 @@ export declare class BrowserPool {
171
119
  hoverLease(sessionId: string, x: number, y: number): Promise<boolean>;
172
120
  /** Set a live lease's media features, or false when this is not a lease or its page cannot. */
173
121
  emulateLease(sessionId: string, media: LeaseMedia): Promise<boolean>;
122
+ /** Whether this lease's page is still loading. False for a non-lease, or a page that cannot say. */
123
+ leaseLoading(sessionId: string): Promise<boolean>;
174
124
  /** Whether this session is a lease whose page has a real keyboard. */
175
125
  hasKeyboard(sessionId: string): boolean;
176
126
  /**