@reticlehq/browser 2.13.1 → 3.1.0
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/actions/actions-dom.d.ts +8 -20
- package/dist/actions/actions-dom.js +70 -69
- package/dist/actions/actions-press.d.ts +14 -0
- package/dist/actions/actions-press.js +123 -0
- package/dist/actions/actions.d.ts +1 -89
- package/dist/actions/actions.js +64 -365
- package/dist/actions/appeared-text.d.ts +0 -20
- package/dist/actions/appeared-text.js +6 -36
- package/dist/actions/danger-context.d.ts +3 -0
- package/dist/actions/danger-context.js +39 -0
- package/dist/actions/scroll.d.ts +8 -10
- package/dist/actions/scroll.js +28 -17
- package/dist/actions/synthetic/synthetic-input.d.ts +2 -0
- package/dist/actions/synthetic/synthetic-input.js +13 -0
- package/dist/actions/value-input.d.ts +0 -30
- package/dist/actions/value-input.js +0 -33
- package/dist/commands/commands.d.ts +0 -2
- package/dist/commands/commands.js +10 -70
- package/dist/connect-options.d.ts +2 -96
- package/dist/dom/a11y.d.ts +0 -29
- package/dist/dom/a11y.js +3 -90
- package/dist/dom/addressing/auto-anchor.d.ts +25 -0
- package/dist/dom/addressing/auto-anchor.js +41 -0
- package/dist/dom/addressing/refs.d.ts +14 -0
- package/dist/dom/addressing/refs.js +114 -0
- package/dist/dom/addressing/source.d.ts +9 -0
- package/dist/dom/addressing/source.js +29 -0
- package/dist/dom/chart.d.ts +0 -61
- package/dist/dom/chart.js +0 -60
- package/dist/dom/desktop-capture.d.ts +0 -30
- package/dist/dom/desktop-capture.js +1 -73
- package/dist/dom/dom-ignore.d.ts +0 -30
- package/dist/dom/dom-ignore.js +1 -48
- package/dist/dom/occlusion.d.ts +0 -7
- package/dist/dom/occlusion.js +31 -12
- package/dist/dom/paint-context.d.ts +6 -0
- package/dist/dom/paint-context.js +38 -0
- package/dist/dom/query.d.ts +0 -14
- package/dist/dom/query.js +50 -216
- package/dist/dom/realm.d.ts +0 -20
- package/dist/dom/realm.js +0 -28
- package/dist/dom/shadow-registry.d.ts +0 -33
- package/dist/dom/shadow-registry.js +2 -72
- package/dist/dom/snapshot.d.ts +2 -43
- package/dist/dom/snapshot.js +27 -152
- package/dist/dom/theme.d.ts +0 -37
- package/dist/dom/theme.js +2 -52
- package/dist/dom/virtualized.d.ts +0 -17
- package/dist/dom/virtualized.js +0 -27
- package/dist/edit-epoch.d.ts +1 -23
- package/dist/edit-epoch.js +1 -53
- package/dist/index.d.ts +4 -5
- package/dist/index.js +4 -28
- package/dist/observers/animation.d.ts +0 -1
- package/dist/observers/animation.js +1 -4
- package/dist/observers/blind-spots.d.ts +0 -26
- package/dist/observers/blind-spots.js +3 -62
- package/dist/observers/console.d.ts +1 -2
- package/dist/observers/console.js +41 -30
- package/dist/observers/context-open.d.ts +1 -11
- package/dist/observers/context-open.js +10 -14
- package/dist/observers/departure.d.ts +3 -0
- package/dist/observers/departure.js +52 -0
- package/dist/observers/dialogs.d.ts +2 -0
- package/dist/observers/dialogs.js +39 -0
- package/dist/observers/dom.js +28 -52
- package/dist/observers/download.d.ts +1 -2
- package/dist/observers/download.js +39 -56
- package/dist/observers/focus.d.ts +0 -6
- package/dist/observers/focus.js +0 -7
- package/dist/observers/frames.d.ts +0 -9
- package/dist/observers/frames.js +1 -28
- package/dist/observers/health.d.ts +0 -5
- package/dist/observers/health.js +2 -34
- package/dist/observers/install-all.d.ts +2 -0
- package/dist/observers/install-all.js +26 -27
- package/dist/observers/ipc.d.ts +0 -25
- package/dist/observers/ipc.js +0 -65
- package/dist/observers/navigation.d.ts +2 -0
- package/dist/observers/navigation.js +33 -0
- package/dist/observers/net-detail/network-body.d.ts +12 -0
- package/dist/observers/net-detail/network-body.js +71 -0
- package/dist/observers/net-detail/network-redact.d.ts +1 -0
- package/dist/observers/net-detail/network-redact.js +1 -0
- package/dist/observers/network-stream.d.ts +0 -21
- package/dist/observers/network-stream.js +3 -46
- package/dist/observers/network.d.ts +3 -27
- package/dist/observers/network.js +178 -295
- package/dist/observers/perf.d.ts +0 -10
- package/dist/observers/perf.js +0 -16
- package/dist/observers/route.d.ts +1 -2
- package/dist/observers/route.js +13 -19
- package/dist/observers/scroll.d.ts +0 -1
- package/dist/observers/scroll.js +2 -11
- package/dist/observers/sdk-failure.d.ts +1 -9
- package/dist/observers/sdk-failure.js +1 -30
- package/dist/observers/state.d.ts +1 -11
- package/dist/observers/state.js +13 -41
- package/dist/observers/storage.d.ts +0 -14
- package/dist/observers/storage.js +10 -34
- package/dist/observers/types.d.ts +0 -15
- package/dist/observers/types.js +0 -14
- package/dist/patching/capture-method.d.ts +0 -24
- package/dist/patching/capture-method.js +0 -26
- package/dist/presenter/chrome/layers.d.ts +2 -0
- package/dist/presenter/chrome/layers.js +2 -0
- package/dist/presenter/chrome/log-kinds.d.ts +12 -0
- package/dist/presenter/chrome/log-kinds.js +7 -0
- package/dist/presenter/{overlay.d.ts → chrome/overlay.d.ts} +0 -1
- package/dist/presenter/{overlay.js → chrome/overlay.js} +0 -1
- package/dist/presenter/chrome/presenter-brand.d.ts +3 -0
- package/dist/presenter/{presenter-brand.js → chrome/presenter-brand.js} +0 -21
- package/dist/presenter/chrome/presenter-effects.d.ts +4 -0
- package/dist/presenter/{presenter-effects.js → chrome/presenter-effects.js} +1 -10
- package/dist/presenter/chrome/presenter-hud-chrome.d.ts +7 -0
- package/dist/presenter/{presenter-hud-chrome.js → chrome/presenter-hud-chrome.js} +0 -10
- package/dist/presenter/chrome/presenter-log.d.ts +17 -0
- package/dist/presenter/{presenter-log.js → chrome/presenter-log.js} +5 -54
- package/dist/presenter/chrome/presenter-report-copy.d.ts +51 -0
- package/dist/presenter/chrome/presenter-report-copy.js +95 -0
- package/dist/presenter/chrome/presenter-safe-html.d.ts +2 -0
- package/dist/presenter/chrome/presenter-safe-html.js +16 -0
- package/dist/presenter/{presenter-tally.d.ts → chrome/presenter-tally.d.ts} +0 -5
- package/dist/presenter/{presenter-tally.js → chrome/presenter-tally.js} +1 -5
- package/dist/presenter/{presenter-verbs.js → chrome/presenter-verbs.js} +0 -7
- package/dist/presenter/{presenter-heroicons-data.d.ts → icons/presenter-heroicons-data.d.ts} +1 -8
- package/dist/presenter/{presenter-heroicons-data.js → icons/presenter-heroicons-data.js} +1 -8
- package/dist/presenter/{presenter-icons.d.ts → icons/presenter-icons.d.ts} +1 -6
- package/dist/presenter/{presenter-icons.js → icons/presenter-icons.js} +1 -6
- package/dist/presenter/presenter-account-styles.d.ts +1 -0
- package/dist/presenter/presenter-account-styles.js +99 -0
- package/dist/presenter/presenter-account.d.ts +40 -0
- package/dist/presenter/presenter-account.js +178 -0
- package/dist/presenter/presenter-config.d.ts +10 -107
- package/dist/presenter/presenter-config.js +5 -92
- package/dist/presenter/presenter-controls.d.ts +1 -35
- package/dist/presenter/presenter-controls.js +10 -133
- package/dist/presenter/presenter-dock-layout.d.ts +0 -3
- package/dist/presenter/presenter-dock-layout.js +1 -5
- package/dist/presenter/presenter-drag.d.ts +2 -42
- package/dist/presenter/presenter-drag.js +3 -76
- package/dist/presenter/presenter-glow.d.ts +0 -21
- package/dist/presenter/presenter-glow.js +2 -28
- package/dist/presenter/presenter-hud-position.d.ts +18 -0
- package/dist/presenter/presenter-hud-position.js +50 -0
- package/dist/presenter/presenter-report-styles.d.ts +0 -6
- package/dist/presenter/presenter-report-styles.js +22 -7
- package/dist/presenter/presenter-report.d.ts +5 -7
- package/dist/presenter/presenter-report.js +44 -95
- package/dist/presenter/presenter-run-state.d.ts +1 -8
- package/dist/presenter/presenter-run-state.js +1 -6
- package/dist/presenter/presenter-settings-styles.d.ts +0 -1
- package/dist/presenter/presenter-settings-styles.js +2 -2
- package/dist/presenter/presenter-settings.d.ts +5 -23
- package/dist/presenter/presenter-settings.js +21 -33
- package/dist/presenter/presenter-shell-styles.js +23 -20
- package/dist/presenter/presenter-shell.d.ts +6 -18
- package/dist/presenter/presenter-shell.js +26 -77
- package/dist/presenter/presenter-styles.d.ts +2 -10
- package/dist/presenter/presenter-styles.js +6 -12
- package/dist/presenter/presenter-workspace.d.ts +4 -4
- package/dist/presenter/presenter-workspace.js +30 -10
- package/dist/presenter/presenter.d.ts +3 -63
- package/dist/presenter/presenter.js +29 -132
- package/dist/presenter/tour/tour-view.d.ts +33 -0
- package/dist/presenter/tour/tour-view.js +188 -0
- package/dist/presenter/tour/tour.d.ts +25 -0
- package/dist/presenter/tour/tour.js +245 -0
- package/dist/recorder/recorder-styles.d.ts +0 -6
- package/dist/recorder/recorder-styles.js +0 -6
- package/dist/recorder/recorder.d.ts +0 -34
- package/dist/recorder/recorder.js +6 -49
- package/dist/registry/auto-testids.d.ts +0 -19
- package/dist/registry/auto-testids.js +1 -21
- package/dist/registry/capabilities.d.ts +0 -31
- package/dist/registry/capabilities.js +1 -46
- package/dist/registry/commit-and-signal.d.ts +0 -6
- package/dist/registry/commit-and-signal.js +1 -7
- package/dist/registry/declared-channels.d.ts +2 -0
- package/dist/registry/declared-channels.js +14 -0
- package/dist/registry/declared-platform.d.ts +4 -0
- package/dist/registry/declared-platform.js +34 -0
- package/dist/registry/domains.d.ts +0 -8
- package/dist/registry/domains.js +0 -7
- package/dist/registry/emitter.d.ts +0 -14
- package/dist/registry/emitter.js +1 -11
- package/dist/registry/store-adapters.d.ts +0 -186
- package/dist/registry/store-adapters.js +1 -164
- package/dist/registry/{adapters.d.ts → stores/adapters.d.ts} +0 -10
- package/dist/registry/{adapters.js → stores/adapters.js} +0 -10
- package/dist/registry/stores.d.ts +0 -33
- package/dist/registry/stores.js +1 -60
- package/dist/reticle-presenter-helpers.d.ts +0 -13
- package/dist/reticle-presenter-helpers.js +10 -20
- package/dist/reticle-singleton.d.ts +2 -0
- package/dist/reticle-singleton.js +3 -0
- package/dist/reticle.d.ts +1 -52
- package/dist/reticle.js +115 -180
- package/dist/review/annotator-styles.js +1 -8
- package/dist/review/annotator.d.ts +2 -13
- package/dist/review/annotator.js +2 -42
- package/dist/review/mark-anchor.d.ts +0 -11
- package/dist/review/mark-anchor.js +3 -11
- package/dist/security/serialization.d.ts +0 -20
- package/dist/security/serialization.js +1 -66
- package/dist/session-continuity.d.ts +0 -16
- package/dist/session-continuity.js +1 -20
- package/dist/timers/clock.d.ts +1 -14
- package/dist/timers/clock.js +59 -60
- package/dist/timers/console/native-console.js +5 -0
- package/dist/timers/native/native-timers.d.ts +11 -0
- package/dist/timers/native/native-timers.js +48 -0
- package/dist/transport/transport.d.ts +0 -51
- package/dist/transport/transport.js +5 -131
- package/dist/transport/unreachable-message.d.ts +0 -17
- package/dist/transport/unreachable-message.js +0 -17
- package/dist/util/captured-method.d.ts +0 -35
- package/dist/util/captured-method.js +0 -35
- package/package.json +5 -4
- package/dist/actions/synthetic-input.d.ts +0 -9
- package/dist/actions/synthetic-input.js +0 -40
- package/dist/dom/auto-anchor.d.ts +0 -65
- package/dist/dom/auto-anchor.js +0 -74
- package/dist/dom/refs.d.ts +0 -90
- package/dist/dom/refs.js +0 -245
- package/dist/dom/source.d.ts +0 -53
- package/dist/dom/source.js +0 -74
- package/dist/observers/network-body.d.ts +0 -35
- package/dist/observers/network-body.js +0 -168
- package/dist/observers/network-redact.d.ts +0 -20
- package/dist/observers/network-redact.js +0 -80
- package/dist/presenter/presenter-brand.d.ts +0 -22
- package/dist/presenter/presenter-effects.d.ts +0 -13
- package/dist/presenter/presenter-hud-chrome.d.ts +0 -17
- package/dist/presenter/presenter-log.d.ts +0 -64
- package/dist/presenter/presenter-report-copy.d.ts +0 -112
- package/dist/presenter/presenter-report-copy.js +0 -156
- package/dist/presenter/presenter-test-helpers.d.ts +0 -16
- package/dist/presenter/presenter-test-helpers.js +0 -41
- package/dist/timers/native-console.js +0 -9
- package/dist/timers/native-timers.d.ts +0 -36
- package/dist/timers/native-timers.js +0 -78
- /package/dist/presenter/{presenter-verbs.d.ts → chrome/presenter-verbs.d.ts} +0 -0
- /package/dist/timers/{native-console.d.ts → console/native-console.d.ts} +0 -0
package/dist/reticle.d.ts
CHANGED
|
@@ -2,40 +2,17 @@ import { EventType, RETICLE_URL_PARAM, type ReticleEvent } from '@reticlehq/core
|
|
|
2
2
|
import { type CapabilitiesInput } from './registry/capabilities.js';
|
|
3
3
|
import type { ReticleConnectOptions } from './connect-options.js';
|
|
4
4
|
export type { ReticleConnectOptions };
|
|
5
|
-
/**
|
|
6
|
-
* Runtime backstop for the dev-only SDK: block connecting when the build reports production, unless
|
|
7
|
-
* explicitly overridden. Pure so it's testable; connect reads NODE_ENV safely (process may be absent
|
|
8
|
-
* in a raw browser). This is defense-in-depth - the primary guard is the consumer gating the import
|
|
9
|
-
* behind `import.meta.env.DEV` so the SDK is dead-code-eliminated from prod bundles entirely.
|
|
10
|
-
*/
|
|
11
5
|
export declare function shouldBlockProduction(nodeEnv: string | undefined, allowInProduction: boolean): boolean;
|
|
12
|
-
export declare function connectionPolicy(pageHostname: string, bridgeUrl: string, allowNonLocalhost: boolean, token: string | undefined,
|
|
13
|
-
/** The page's protocol. Desktop webviews (file:, app:, tauri:) are local despite a non-loopback host. */
|
|
14
|
-
pageProtocol?: string): {
|
|
6
|
+
export declare function connectionPolicy(pageHostname: string, bridgeUrl: string, allowNonLocalhost: boolean, token: string | undefined, pageProtocol?: string): {
|
|
15
7
|
allowed: boolean;
|
|
16
8
|
reason?: string;
|
|
17
9
|
};
|
|
18
|
-
/**
|
|
19
|
-
* Resolve the session label. An absent label or the `auto` sentinel yields a fresh per-tab id (via
|
|
20
|
-
* the injected generator) so multi-tab / new-tab routes never collide; any other label is used
|
|
21
|
-
* verbatim so tabs can intentionally share a session. `gen` is injected to keep this clock-free.
|
|
22
|
-
*/
|
|
23
10
|
export declare function resolveSessionLabel(option: string | undefined, gen: () => string): string;
|
|
24
11
|
export { RETICLE_URL_PARAM };
|
|
25
|
-
/**
|
|
26
|
-
* Extract Reticle identity overrides from a `location.search` string. Pure (takes the string, not the
|
|
27
|
-
* window) so it's testable without a DOM. Explicit connect options still win over these.
|
|
28
|
-
*/
|
|
29
12
|
export declare function reticleParamsFromSearch(search: string): {
|
|
30
13
|
session?: string;
|
|
31
14
|
projectId?: string;
|
|
32
15
|
};
|
|
33
|
-
/**
|
|
34
|
-
* Resolve the session + project identity for a connection: an explicit, non-`auto` option wins;
|
|
35
|
-
* otherwise a launcher-stamped URL param is used; otherwise undefined (caller generates a per-tab id).
|
|
36
|
-
* Crucially `auto` is treated like "unset" so an app that passes the auto sentinel still lets a pooled
|
|
37
|
-
* launcher correlate the lease via __reticle_session. Pure for testability.
|
|
38
|
-
*/
|
|
39
16
|
export declare function resolveConnectIdentity(options: {
|
|
40
17
|
session?: string;
|
|
41
18
|
projectId?: string;
|
|
@@ -43,11 +20,6 @@ export declare function resolveConnectIdentity(options: {
|
|
|
43
20
|
session: string | undefined;
|
|
44
21
|
projectId: string | undefined;
|
|
45
22
|
};
|
|
46
|
-
/**
|
|
47
|
-
* Assemble an event envelope. Pure: `seq` (monotonic per session) and `t` (elapsed clock) are
|
|
48
|
-
* injected, never read here - so it is unit-testable and honors the clock-injection rule. The
|
|
49
|
-
* causing action's id is attributed server-side by the settle window, so it is not stamped here.
|
|
50
|
-
*/
|
|
51
23
|
export declare function buildEvent(args: {
|
|
52
24
|
seq: number;
|
|
53
25
|
t: number;
|
|
@@ -58,38 +30,15 @@ export declare function buildEvent(args: {
|
|
|
58
30
|
documentId?: string | undefined;
|
|
59
31
|
editEpoch?: number | undefined;
|
|
60
32
|
}): ReticleEvent;
|
|
61
|
-
/**
|
|
62
|
-
* The browser-side orchestrator. Wires observers -> events -> bridge, and bridge
|
|
63
|
-
* commands -> handlers. Embedded in the host app (dev only).
|
|
64
|
-
*/
|
|
65
33
|
export declare class Reticle {
|
|
66
34
|
#private;
|
|
67
35
|
connect(options?: ReticleConnectOptions): void;
|
|
68
|
-
/** Whether the in-page SDK is connected to the bridge (read by createReticleEmitter, P5a). */
|
|
69
36
|
get connected(): boolean;
|
|
70
|
-
/** Surface an arbitrary app-domain observation the DOM can't express. */
|
|
71
37
|
signal(name: string, data?: Record<string, unknown>): void;
|
|
72
|
-
/** Report a framework/store state change the agent can observe and assert on. */
|
|
73
38
|
state(name: string, value: unknown): void;
|
|
74
|
-
/**
|
|
75
|
-
* Report an aggregated count of React commits (the @reticlehq/react render meter calls this on a
|
|
76
|
-
* throttle). Emits a single RENDER_COMMIT event per window so commit storms are observable without a
|
|
77
|
-
* per-render flood. Dev-only, like the whole SDK.
|
|
78
|
-
*/
|
|
79
39
|
renderCommit(commits: number): void;
|
|
80
|
-
/** Advertise the app's testable surface so the agent learns it without reading source. */
|
|
81
40
|
describe(input: CapabilitiesInput): void;
|
|
82
|
-
/** Live-control: end the session programmatically from the host app (drives the panel to ended). */
|
|
83
41
|
endSession(): void;
|
|
84
|
-
/**
|
|
85
|
-
* Hand the SDK the page's hot-update channel, so a stale ref can say the code changed underneath it.
|
|
86
|
-
*
|
|
87
|
-
* `unknown` on purpose: the only caller that has one is the build integration, the shape it passes
|
|
88
|
-
* is Vite's `import.meta.hot`, and the SDK must not depend on Vite — it ships to Next, Electron,
|
|
89
|
-
* Tauri and plain pages. Anything that is not a subscribable channel is ignored, which is the
|
|
90
|
-
* normal case: with no channel the epoch stays at `NO_EDITS_OBSERVED`, meaning "no edits OBSERVED",
|
|
91
|
-
* never "no edits happened".
|
|
92
|
-
*/
|
|
93
42
|
observeHotUpdates(hot: unknown): void;
|
|
94
43
|
disconnect(): void;
|
|
95
44
|
}
|
package/dist/reticle.js
CHANGED
|
@@ -1,34 +1,27 @@
|
|
|
1
1
|
import { setPresenterVisible } from './dom/dom-ignore.js';
|
|
2
|
-
import { EventType, RETICLE_DEFAULT_PORT, RETICLE_PROTOCOL_VERSION, RETICLE_URL_PARAM, bridgeWsUrl, ReticleCommand, MessageKind, SESSION_AUTO, SessionState, TRANSPORT_LIMITS, buildRedactionPolicy, isLoopbackHostname, isLocalPage, setActiveRedactionPolicy, wireRedactionKeys, RETICLE_ROOT_GLOBAL, RETICLE_SDK_VERSION_GLOBAL, CONTRACT_FINGERPRINT, newDocumentId, NO_EDITS_OBSERVED, } from '@reticlehq/core';
|
|
2
|
+
import { EventType, RETICLE_DEFAULT_PORT, RETICLE_PROTOCOL_VERSION, RETICLE_URL_PARAM, bridgeWsUrl, ReticleCommand, MessageKind, SESSION_AUTO, SessionState, TRANSPORT_LIMITS, buildRedactionPolicy, isLoopbackHostname, isLocalPage, setActiveRedactionPolicy, wireRedactionKeys, RETICLE_ROOT_GLOBAL, RETICLE_SDK_VERSION_GLOBAL, CONTRACT_FINGERPRINT, CONTRACT_PARTS, newDocumentId, NO_EDITS_OBSERVED, PresenterMode, } from '@reticlehq/core';
|
|
3
3
|
import { rememberSessionLabel } from './session-continuity.js';
|
|
4
4
|
import { editEpoch } from './edit-epoch.js';
|
|
5
5
|
import { createCommandRegistry, RELOAD_CACHE_BUST_PARAM, } from './commands/commands.js';
|
|
6
6
|
import { Transport } from './transport/transport.js';
|
|
7
7
|
import { unreachableMessage } from './transport/unreachable-message.js';
|
|
8
|
-
import { adapterNames } from './registry/adapters.js';
|
|
8
|
+
import { adapterNames } from './registry/stores/adapters.js';
|
|
9
|
+
import { declaredChannels } from './registry/declared-channels.js';
|
|
10
|
+
import { declaredCommands, declaredPlatform } from './registry/declared-platform.js';
|
|
9
11
|
import { registerCapabilities, setCapabilitiesListener, hasCapabilities, } from './registry/capabilities.js';
|
|
10
|
-
import { installAllObservers } from './observers/install-all.js';
|
|
11
|
-
import { installOverlay } from './presenter/overlay.js';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { actionVerb } from './presenter/presenter-verbs.js';
|
|
12
|
+
import { installAllObservers, runTeardowns } from './observers/install-all.js';
|
|
13
|
+
import { installOverlay } from './presenter/chrome/overlay.js';
|
|
14
|
+
import { LOG_KIND, LOG_RESULT } from './presenter/chrome/log-kinds.js';
|
|
15
|
+
import { actionVerb } from './presenter/chrome/presenter-verbs.js';
|
|
15
16
|
import { str, refLabel, modeForCommand, presentStatus } from './reticle-presenter-helpers.js';
|
|
16
17
|
import { resetClock } from './timers/clock.js';
|
|
17
|
-
import { nativeWarn } from './timers/native-console.js';
|
|
18
|
+
import { nativeWarn } from './timers/console/native-console.js';
|
|
18
19
|
import { installRecorder } from './recorder/recorder.js';
|
|
19
20
|
import { Annotator } from './review/annotator.js';
|
|
20
|
-
/**
|
|
21
|
-
* Runtime backstop for the dev-only SDK: block connecting when the build reports production, unless
|
|
22
|
-
* explicitly overridden. Pure so it's testable; connect reads NODE_ENV safely (process may be absent
|
|
23
|
-
* in a raw browser). This is defense-in-depth - the primary guard is the consumer gating the import
|
|
24
|
-
* behind `import.meta.env.DEV` so the SDK is dead-code-eliminated from prod bundles entirely.
|
|
25
|
-
*/
|
|
26
21
|
export function shouldBlockProduction(nodeEnv, allowInProduction) {
|
|
27
22
|
return 'production' === nodeEnv && !allowInProduction;
|
|
28
23
|
}
|
|
29
|
-
export function connectionPolicy(pageHostname, bridgeUrl, allowNonLocalhost, token,
|
|
30
|
-
/** The page's protocol. Desktop webviews (file:, app:, tauri:) are local despite a non-loopback host. */
|
|
31
|
-
pageProtocol = 'http:') {
|
|
24
|
+
export function connectionPolicy(pageHostname, bridgeUrl, allowNonLocalhost, token, pageProtocol = 'http:') {
|
|
32
25
|
let bridge;
|
|
33
26
|
try {
|
|
34
27
|
bridge = new URL(bridgeUrl);
|
|
@@ -63,19 +56,11 @@ pageProtocol = 'http:') {
|
|
|
63
56
|
}
|
|
64
57
|
return { allowed: true };
|
|
65
58
|
}
|
|
66
|
-
/** HUD summary when the SDK self-ends a session because the bridge (server/agent) became unreachable. */
|
|
67
59
|
const BRIDGE_LOST_SUMMARY = 'Session ended - lost connection to Reticle (the agent is no longer running).';
|
|
68
|
-
/**
|
|
69
|
-
* Resolve the session label. An absent label or the `auto` sentinel yields a fresh per-tab id (via
|
|
70
|
-
* the injected generator) so multi-tab / new-tab routes never collide; any other label is used
|
|
71
|
-
* verbatim so tabs can intentionally share a session. `gen` is injected to keep this clock-free.
|
|
72
|
-
*/
|
|
73
60
|
export function resolveSessionLabel(option, gen) {
|
|
74
61
|
return option === undefined || option === SESSION_AUTO ? gen() : option;
|
|
75
62
|
}
|
|
76
|
-
// Re-exported from the protocol (the wire contract) so callers/tests can import it from the SDK too.
|
|
77
63
|
export { RETICLE_URL_PARAM };
|
|
78
|
-
/** Remove the `_reticle_reload` cache-buster a hard REFRESH left behind, via a native replaceState. */
|
|
79
64
|
function stripReloadCacheBustParam() {
|
|
80
65
|
try {
|
|
81
66
|
const current = new URL(window.location.href);
|
|
@@ -85,13 +70,8 @@ function stripReloadCacheBustParam() {
|
|
|
85
70
|
window.history.replaceState(window.history.state, '', current.toString());
|
|
86
71
|
}
|
|
87
72
|
catch {
|
|
88
|
-
/* best-effort URL hygiene - never block connect() on it */
|
|
89
73
|
}
|
|
90
74
|
}
|
|
91
|
-
/**
|
|
92
|
-
* Extract Reticle identity overrides from a `location.search` string. Pure (takes the string, not the
|
|
93
|
-
* window) so it's testable without a DOM. Explicit connect options still win over these.
|
|
94
|
-
*/
|
|
95
75
|
export function reticleParamsFromSearch(search) {
|
|
96
76
|
const params = new URLSearchParams(search);
|
|
97
77
|
const out = {};
|
|
@@ -103,12 +83,6 @@ export function reticleParamsFromSearch(search) {
|
|
|
103
83
|
out.projectId = projectId;
|
|
104
84
|
return out;
|
|
105
85
|
}
|
|
106
|
-
/**
|
|
107
|
-
* Resolve the session + project identity for a connection: an explicit, non-`auto` option wins;
|
|
108
|
-
* otherwise a launcher-stamped URL param is used; otherwise undefined (caller generates a per-tab id).
|
|
109
|
-
* Crucially `auto` is treated like "unset" so an app that passes the auto sentinel still lets a pooled
|
|
110
|
-
* launcher correlate the lease via __reticle_session. Pure for testability.
|
|
111
|
-
*/
|
|
112
86
|
export function resolveConnectIdentity(options, search) {
|
|
113
87
|
const url = reticleParamsFromSearch(search);
|
|
114
88
|
const explicitSession = options.session !== undefined && options.session !== SESSION_AUTO ? options.session : undefined;
|
|
@@ -118,11 +92,6 @@ export function resolveConnectIdentity(options, search) {
|
|
|
118
92
|
projectId: projectId !== undefined && projectId.length > 0 ? projectId : undefined,
|
|
119
93
|
};
|
|
120
94
|
}
|
|
121
|
-
/**
|
|
122
|
-
* Assemble an event envelope. Pure: `seq` (monotonic per session) and `t` (elapsed clock) are
|
|
123
|
-
* injected, never read here - so it is unit-testable and honors the clock-injection rule. The
|
|
124
|
-
* causing action's id is attributed server-side by the settle window, so it is not stamped here.
|
|
125
|
-
*/
|
|
126
95
|
export function buildEvent(args) {
|
|
127
96
|
return {
|
|
128
97
|
t: args.t,
|
|
@@ -130,55 +99,40 @@ export function buildEvent(args) {
|
|
|
130
99
|
type: args.type,
|
|
131
100
|
sessionId: args.sessionId,
|
|
132
101
|
ref: args.ref,
|
|
133
|
-
// Which document this was observed under, so the server can refuse evidence minted before a
|
|
134
|
-
// navigation replaced the page. Stamped HERE because it is the one place every event passes
|
|
135
|
-
// through: an observer added later would otherwise emit unstamped events, which read as
|
|
136
|
-
// "current" by design and would reintroduce the defect silently for one event type.
|
|
137
102
|
documentId: args.documentId,
|
|
138
|
-
// Which round of source edits this was observed under. Stamped HERE for exactly the reason
|
|
139
|
-
// documentId is, and omitted while nothing has hot-updated: absence already reads as "current"
|
|
140
|
-
// downstream, so `NO_EDITS_OBSERVED` on the wire would be bytes spent saying "unknown".
|
|
141
103
|
editEpoch: NO_EDITS_OBSERVED === args.editEpoch ? undefined : args.editEpoch,
|
|
142
104
|
data: args.data,
|
|
143
105
|
};
|
|
144
106
|
}
|
|
145
|
-
/**
|
|
146
|
-
* The browser-side orchestrator. Wires observers -> events -> bridge, and bridge
|
|
147
|
-
* commands -> handlers. Embedded in the host app (dev only).
|
|
148
|
-
*/
|
|
149
107
|
export class Reticle {
|
|
150
108
|
#transport;
|
|
151
109
|
#registry = new Map();
|
|
152
110
|
#teardowns = [];
|
|
153
111
|
#connected = false;
|
|
154
112
|
#session = 'default';
|
|
155
|
-
/**
|
|
156
|
-
* Minted once, here, because this class is constructed once per document: a full navigation tears
|
|
157
|
-
* down the JavaScript context and builds a new one, so the id dies with the document it names. An
|
|
158
|
-
* SPA route change does not reconstruct it, which is correct — same context, same in-flight
|
|
159
|
-
* requests, same evidence.
|
|
160
|
-
*/
|
|
161
113
|
#documentId = newDocumentId(Math.random);
|
|
162
114
|
#start = 0;
|
|
163
115
|
#overlay;
|
|
164
116
|
#presenter;
|
|
117
|
+
#pendingPushes = new Map();
|
|
118
|
+
#readPanelSettings;
|
|
119
|
+
#panelRequested = false;
|
|
120
|
+
#bridgeConnected = false;
|
|
165
121
|
#recorder;
|
|
166
122
|
#annotator;
|
|
167
123
|
#eventCount = 0;
|
|
168
124
|
#token;
|
|
169
125
|
#sdkVersion;
|
|
126
|
+
#captureBodies = false;
|
|
127
|
+
#sourceMapping = undefined;
|
|
170
128
|
#projectId;
|
|
171
|
-
/** App-declared extra redaction keys, announced in hello so the driven path honours them too. */
|
|
172
129
|
#redactKeys = [];
|
|
173
|
-
/** Act-row log handle for the in-flight act/act_sequence, so its outcome stamps the right row. */
|
|
174
130
|
#actHandle;
|
|
175
131
|
connect(options = {}) {
|
|
176
132
|
if (this.#connected)
|
|
177
133
|
return;
|
|
178
134
|
if ('undefined' === typeof window || 'undefined' === typeof document)
|
|
179
135
|
return;
|
|
180
|
-
// Dev-only backstop: refuse to activate in a production build (SSR healthcheck, prod bundle opened
|
|
181
|
-
// on localhost). `process` may not exist in a raw browser, so read NODE_ENV off globalThis.
|
|
182
136
|
const proc = globalThis.process;
|
|
183
137
|
const nodeEnv = proc?.env?.['NODE_ENV'];
|
|
184
138
|
if (shouldBlockProduction(nodeEnv, true === options.allowInProduction)) {
|
|
@@ -186,9 +140,6 @@ export class Reticle {
|
|
|
186
140
|
'import.meta.env.DEV, or pass allowInProduction:true to override.');
|
|
187
141
|
return;
|
|
188
142
|
}
|
|
189
|
-
// A `hard` REFRESH left a cache-busting `_reticle_reload=<nonce>` in the address bar. Strip it now
|
|
190
|
-
// (before the route observer installs, so it emits no spurious ROUTE_CHANGE) - otherwise every hard
|
|
191
|
-
// reload permanently pollutes the URL the app and the agent see.
|
|
192
143
|
stripReloadCacheBustParam();
|
|
193
144
|
const url = options.url ?? bridgeWsUrl(RETICLE_DEFAULT_PORT);
|
|
194
145
|
const policy = connectionPolicy(window.location.hostname, url, true === options.allowNonLocalhost, options.token, window.location.protocol);
|
|
@@ -196,24 +147,15 @@ export class Reticle {
|
|
|
196
147
|
globalThis.console.warn(`[Reticle] ${policy.reason ?? 'connection blocked'}`);
|
|
197
148
|
return;
|
|
198
149
|
}
|
|
199
|
-
// Publish the project root for the adapters' source relativisation. Done HERE, in library code,
|
|
200
|
-
// so the app-side connect stays plain JavaScript - see ReticleConnectOptions.root.
|
|
201
150
|
if (options.root !== undefined && options.root.length > 0) {
|
|
202
151
|
globalThis[RETICLE_ROOT_GLOBAL] = options.root;
|
|
203
152
|
}
|
|
204
|
-
// The build plugin defines this; a hand-wired connect can pass it explicitly. Either way an
|
|
205
|
-
// absent value means UNKNOWN, so the bridge never reports an unknown pair as in sync.
|
|
206
153
|
const declaredVersion = options.sdkVersion ?? globalThis[RETICLE_SDK_VERSION_GLOBAL];
|
|
207
154
|
this.#sdkVersion =
|
|
208
155
|
'string' === typeof declaredVersion && declaredVersion.length > 0
|
|
209
156
|
? declaredVersion
|
|
210
157
|
: undefined;
|
|
211
|
-
// A pooled/headless launcher can stamp identity via namespaced URL params; explicit (non-auto)
|
|
212
|
-
// options win, but the `auto` sentinel defers to the URL param so leases correlate.
|
|
213
158
|
const identity = resolveConnectIdentity(options, window.location.search);
|
|
214
|
-
// Remembered per TAB, so a reload rejoins the same session instead of appearing as a new one and
|
|
215
|
-
// stranding the agent's handle - see session-continuity. An explicit id (connect option, or the
|
|
216
|
-
// lease's URL param) still wins.
|
|
217
159
|
const explicitSession = resolveSessionLabel(identity.session, () => '');
|
|
218
160
|
this.#session = rememberSessionLabel(explicitSession.length > 0 ? explicitSession : undefined, 'undefined' === typeof globalThis.sessionStorage ? undefined : globalThis.sessionStorage, () => 'function' === typeof globalThis.crypto?.randomUUID
|
|
219
161
|
? `s${globalThis.crypto.randomUUID()}`
|
|
@@ -221,9 +163,6 @@ export class Reticle {
|
|
|
221
163
|
this.#token =
|
|
222
164
|
options.token !== undefined && options.token.length > 0 ? options.token : undefined;
|
|
223
165
|
this.#projectId = identity.projectId;
|
|
224
|
-
// Install the redaction rule BEFORE anything can emit. Every observer, the serializer and the
|
|
225
|
-
// snapshot path read it ambiently, so a policy set even one line after installAllObservers would
|
|
226
|
-
// leave the first events of the session redacted by a different rule than the rest.
|
|
227
166
|
this.#applyRedaction(options.redact);
|
|
228
167
|
this.#start = performance.now();
|
|
229
168
|
this.#registry = createCommandRegistry();
|
|
@@ -231,85 +170,49 @@ export class Reticle {
|
|
|
231
170
|
url,
|
|
232
171
|
hello: () => this.#hello(),
|
|
233
172
|
handleCommand: (command) => this.#handleCommand(command),
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
// no server-pushed end can arrive - so end the run we're presenting ourselves. A returning
|
|
239
|
-
// agent revives it via the normal sessionStart path on its next command.
|
|
173
|
+
onConnected: () => {
|
|
174
|
+
this.#bridgeConnected = true;
|
|
175
|
+
this.#presenter?.sessionStart();
|
|
176
|
+
},
|
|
240
177
|
onConnectionLost: () => {
|
|
241
|
-
// Restore real timers. A frozen clock is driven by the agent through the bridge; once the
|
|
242
|
-
// bridge is gone nothing can ever advance it, so leaving it installed pins Date.now and
|
|
243
|
-
// queues every setTimeout into a scheduler that will never run. Concretely that kills every
|
|
244
|
-
// lodash debounce/throttle in the app (now - lastCall stays 0), so search boxes, autosave
|
|
245
|
-
// and resize handlers stop firing until a reload - with nothing on screen explaining why.
|
|
246
178
|
resetClock();
|
|
247
179
|
if (true === this.#presenter?.sessionActive) {
|
|
248
180
|
this.#presenter.setState(SessionState.ENDED, BRIDGE_LOST_SUMMARY);
|
|
249
181
|
}
|
|
250
182
|
},
|
|
251
|
-
// First-connect never succeeded ⇒ this page's socket is not opening at this URL. Say what the
|
|
252
|
-
// page observed rather than guessing at the daemon, which it cannot see from in here.
|
|
253
183
|
onUnreachable: ({ url: tried, attempts }) => {
|
|
254
184
|
nativeWarn(unreachableMessage(tried, attempts));
|
|
255
|
-
// And on screen, not only in a console nobody has open. An instrumented page with a dead
|
|
256
|
-
// bridge looked exactly like a page with no Reticle in it, so the user could not tell a
|
|
257
|
-
// daemon they forgot to start from an install that did not work.
|
|
258
185
|
this.#presenter?.showUnreachable(tried, attempts);
|
|
259
186
|
},
|
|
260
187
|
});
|
|
261
|
-
// Capabilities registered AFTER connect (which is when they are registered, by design) must
|
|
262
|
-
// reach the bridge, or an app that declared its whole testable surface reads as having none.
|
|
263
188
|
setCapabilitiesListener(() => this.#transport?.reannounce());
|
|
264
|
-
// Contributors only, and reported in capabilities so a verdict drawn with it open is never
|
|
265
|
-
// mistaken for an ordinary one. See connect-options.ts.
|
|
266
189
|
setPresenterVisible(true === options.exposePresenter);
|
|
267
190
|
const emit = this.#emit;
|
|
191
|
+
this.#captureBodies = true === options.captureNetworkBodies;
|
|
192
|
+
this.#sourceMapping =
|
|
193
|
+
'boolean' === typeof options.sourceMapping ? options.sourceMapping : undefined;
|
|
268
194
|
this.#teardowns = installAllObservers(emit, {
|
|
269
|
-
captureBodies:
|
|
195
|
+
captureBodies: this.#captureBodies,
|
|
196
|
+
...(options.captureErrorBodies === undefined
|
|
197
|
+
? {}
|
|
198
|
+
: { captureErrorBodies: options.captureErrorBodies }),
|
|
270
199
|
});
|
|
271
200
|
if (true === options.overlay) {
|
|
272
201
|
this.#overlay = installOverlay();
|
|
273
202
|
this.#overlay.update({ connected: true, events: 0 });
|
|
274
203
|
}
|
|
275
|
-
if (options.present !== false)
|
|
276
|
-
|
|
277
|
-
if (options.pace !== undefined)
|
|
278
|
-
presenterOptions.paceMs = options.pace;
|
|
279
|
-
if (options.narrationDwellMs !== undefined) {
|
|
280
|
-
presenterOptions.narrationDwellMs = options.narrationDwellMs;
|
|
281
|
-
}
|
|
282
|
-
if (options.border !== undefined)
|
|
283
|
-
presenterOptions.border = options.border;
|
|
284
|
-
if (options.logMax !== undefined)
|
|
285
|
-
presenterOptions.logMax = options.logMax;
|
|
286
|
-
if (options.endedFadeMs !== undefined)
|
|
287
|
-
presenterOptions.endedFadeMs = options.endedFadeMs;
|
|
288
|
-
if (options.idleEndMs !== undefined)
|
|
289
|
-
presenterOptions.idleEndMs = options.idleEndMs;
|
|
290
|
-
presenterOptions.sessionId = this.#session;
|
|
291
|
-
// The panel calls this when the human pauses/resumes/ends or sends a message. We emit a
|
|
292
|
-
// HUMAN_CONTROL event over the existing transport; #emit stamps `t` from the elapsed clock.
|
|
293
|
-
presenterOptions.onControl = (intent) => this.#emit(EventType.HUMAN_CONTROL, intent.text !== undefined
|
|
294
|
-
? { kind: intent.kind, text: intent.text }
|
|
295
|
-
: { kind: intent.kind });
|
|
296
|
-
this.#presenter = new Presenter(presenterOptions);
|
|
297
|
-
// Mount the overlay. The session (glow + HUD) activates on bridge connect via onConnected,
|
|
298
|
-
// so the presenter is visible as soon as the agent is reachable - not just on first command.
|
|
299
|
-
this.#presenter.mount();
|
|
300
|
-
}
|
|
204
|
+
if (options.present !== false)
|
|
205
|
+
this.#fetchAndShowPanel(options);
|
|
301
206
|
if (true === options.recorder) {
|
|
302
207
|
this.#recorder = installRecorder({ emit, now: () => Date.now() });
|
|
303
208
|
this.#recorder.mount();
|
|
304
209
|
}
|
|
305
|
-
// The page annotator rides with the presenter (the human surface) unless explicitly off.
|
|
306
210
|
if (options.annotate ?? options.present !== false) {
|
|
307
|
-
const presenter = this.#presenter;
|
|
308
211
|
this.#annotator = new Annotator({
|
|
309
212
|
emit,
|
|
310
213
|
now: () => Date.now(),
|
|
311
|
-
onMark: (mark) => presenter?.log(LOG_KIND.HUMAN, `🚩 #${String(mark.index)} ${mark.anchor}${mark.source !== undefined ? ` · ${mark.source}` : ''} — ${mark.note}`),
|
|
312
|
-
shouldBlock: () =>
|
|
214
|
+
onMark: (mark) => this.#presenter?.log(LOG_KIND.HUMAN, `🚩 #${String(mark.index)} ${mark.anchor}${mark.source !== undefined ? ` · ${mark.source}` : ''} — ${mark.note}`),
|
|
215
|
+
shouldBlock: () => this.#readPanelSettings?.().blockPageInteractions ?? true,
|
|
313
216
|
});
|
|
314
217
|
this.#annotator.mount();
|
|
315
218
|
this.#presenter?.bindAnnotator(this.#annotator);
|
|
@@ -317,65 +220,106 @@ export class Reticle {
|
|
|
317
220
|
this.#transport.connect();
|
|
318
221
|
this.#connected = true;
|
|
319
222
|
}
|
|
320
|
-
|
|
223
|
+
#fetchAndShowPanel(options) {
|
|
224
|
+
if (this.#panelRequested)
|
|
225
|
+
return;
|
|
226
|
+
this.#panelRequested = true;
|
|
227
|
+
void import('./presenter/presenter.js')
|
|
228
|
+
.then(({ Presenter }) => import('./presenter/presenter-settings.js').then((settings) => ({ Presenter, settings })))
|
|
229
|
+
.then(({ Presenter, settings }) => {
|
|
230
|
+
if (!this.#connected)
|
|
231
|
+
return;
|
|
232
|
+
this.#readPanelSettings = settings.getPresenterSettings;
|
|
233
|
+
const panelOptions = {};
|
|
234
|
+
if (options.pace !== undefined)
|
|
235
|
+
panelOptions.paceMs = options.pace;
|
|
236
|
+
if (options.narrationDwellMs !== undefined) {
|
|
237
|
+
panelOptions.narrationDwellMs = options.narrationDwellMs;
|
|
238
|
+
}
|
|
239
|
+
if (options.border !== undefined)
|
|
240
|
+
panelOptions.border = options.border;
|
|
241
|
+
if (options.logMax !== undefined)
|
|
242
|
+
panelOptions.logMax = options.logMax;
|
|
243
|
+
if (options.endedFadeMs !== undefined)
|
|
244
|
+
panelOptions.endedFadeMs = options.endedFadeMs;
|
|
245
|
+
if (options.idleEndMs !== undefined)
|
|
246
|
+
panelOptions.idleEndMs = options.idleEndMs;
|
|
247
|
+
panelOptions.sessionId = this.#session;
|
|
248
|
+
panelOptions.onControl = (intent) => this.#emit(EventType.HUMAN_CONTROL, intent.text !== undefined
|
|
249
|
+
? { kind: intent.kind, text: intent.text }
|
|
250
|
+
: { kind: intent.kind });
|
|
251
|
+
const panel = new Presenter(panelOptions);
|
|
252
|
+
this.#presenter = panel;
|
|
253
|
+
panel.mount();
|
|
254
|
+
for (const buffered of this.#pendingPushes.values())
|
|
255
|
+
panel.handlePush(buffered);
|
|
256
|
+
this.#pendingPushes.clear();
|
|
257
|
+
void import('./presenter/tour/tour.js').then((tour) => {
|
|
258
|
+
tour.mountTour({
|
|
259
|
+
document,
|
|
260
|
+
storage: tour.safeLocalStorage(),
|
|
261
|
+
projectId: reticleParamsFromSearch(window.location.search).projectId ?? window.location.origin,
|
|
262
|
+
isDriving: () => PresenterMode.IDLE !== panel.mode,
|
|
263
|
+
search: window.location.search,
|
|
264
|
+
copy: (text) => navigator.clipboard?.writeText(text).then(() => true, () => false) ?? false,
|
|
265
|
+
select: (element) => {
|
|
266
|
+
const selection = window.getSelection();
|
|
267
|
+
if (null === selection)
|
|
268
|
+
return;
|
|
269
|
+
const range = document.createRange();
|
|
270
|
+
range.selectNodeContents(element);
|
|
271
|
+
selection.removeAllRanges();
|
|
272
|
+
selection.addRange(range);
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
if (this.#bridgeConnected)
|
|
277
|
+
panel.sessionStart();
|
|
278
|
+
if (this.#annotator !== undefined)
|
|
279
|
+
panel.bindAnnotator(this.#annotator);
|
|
280
|
+
})
|
|
281
|
+
.catch(() => {
|
|
282
|
+
});
|
|
283
|
+
}
|
|
321
284
|
get connected() {
|
|
322
285
|
return this.#connected;
|
|
323
286
|
}
|
|
324
|
-
/** Surface an arbitrary app-domain observation the DOM can't express. */
|
|
325
287
|
signal(name, data = {}) {
|
|
326
288
|
this.#emit(EventType.SIGNAL, { name, data });
|
|
327
289
|
}
|
|
328
|
-
/** Report a framework/store state change the agent can observe and assert on. */
|
|
329
290
|
state(name, value) {
|
|
330
291
|
this.#emit(EventType.STATE_CHANGE, { name, value });
|
|
331
292
|
}
|
|
332
|
-
/**
|
|
333
|
-
* Report an aggregated count of React commits (the @reticlehq/react render meter calls this on a
|
|
334
|
-
* throttle). Emits a single RENDER_COMMIT event per window so commit storms are observable without a
|
|
335
|
-
* per-render flood. Dev-only, like the whole SDK.
|
|
336
|
-
*/
|
|
337
293
|
renderCommit(commits) {
|
|
338
294
|
if (commits > 0)
|
|
339
295
|
this.#emit(EventType.RENDER_COMMIT, { commits });
|
|
340
296
|
}
|
|
341
|
-
/** Advertise the app's testable surface so the agent learns it without reading source. */
|
|
342
297
|
describe(input) {
|
|
343
|
-
registerCapabilities(input);
|
|
298
|
+
registerCapabilities(input);
|
|
344
299
|
}
|
|
345
|
-
/** Live-control: end the session programmatically from the host app (drives the panel to ended). */
|
|
346
300
|
endSession() {
|
|
347
301
|
this.#presenter?.setState(SessionState.ENDED);
|
|
348
302
|
}
|
|
349
|
-
/**
|
|
350
|
-
* Hand the SDK the page's hot-update channel, so a stale ref can say the code changed underneath it.
|
|
351
|
-
*
|
|
352
|
-
* `unknown` on purpose: the only caller that has one is the build integration, the shape it passes
|
|
353
|
-
* is Vite's `import.meta.hot`, and the SDK must not depend on Vite — it ships to Next, Electron,
|
|
354
|
-
* Tauri and plain pages. Anything that is not a subscribable channel is ignored, which is the
|
|
355
|
-
* normal case: with no channel the epoch stays at `NO_EDITS_OBSERVED`, meaning "no edits OBSERVED",
|
|
356
|
-
* never "no edits happened".
|
|
357
|
-
*/
|
|
358
303
|
observeHotUpdates(hot) {
|
|
359
304
|
editEpoch.observe(hot);
|
|
360
305
|
}
|
|
361
306
|
disconnect() {
|
|
362
307
|
if (!this.#connected)
|
|
363
308
|
return;
|
|
364
|
-
|
|
365
|
-
teardown();
|
|
309
|
+
runTeardowns(this.#emit, this.#teardowns);
|
|
366
310
|
this.#teardowns = [];
|
|
367
311
|
this.#transport?.close();
|
|
368
312
|
this.#transport = undefined;
|
|
369
313
|
this.#overlay?.destroy();
|
|
370
314
|
this.#overlay = undefined;
|
|
371
|
-
this.#presenter?.sessionEnd();
|
|
315
|
+
this.#presenter?.sessionEnd();
|
|
372
316
|
this.#presenter?.destroy();
|
|
373
317
|
this.#presenter = undefined;
|
|
374
318
|
this.#recorder?.destroy();
|
|
375
319
|
this.#recorder = undefined;
|
|
376
320
|
this.#annotator?.destroy();
|
|
377
321
|
this.#annotator = undefined;
|
|
378
|
-
resetClock();
|
|
322
|
+
resetClock();
|
|
379
323
|
this.#connected = false;
|
|
380
324
|
}
|
|
381
325
|
#emit = (type, data, ref) => {
|
|
@@ -389,29 +333,16 @@ export class Reticle {
|
|
|
389
333
|
data,
|
|
390
334
|
ref,
|
|
391
335
|
});
|
|
392
|
-
// Guarded because #emit runs INLINE IN THE APP'S CALL STACK: every monkey-patch calls it from
|
|
393
|
-
// inside the function it replaced. An exception here does not surface as an SDK error - it
|
|
394
|
-
// propagates out of history.pushState (crashing a router's navigate), out of localStorage.setItem
|
|
395
|
-
// after the write already succeeded, or out of console.log before the message reaches the console.
|
|
396
|
-
// A dev-only observability SDK must never be able to break the app it is observing.
|
|
397
336
|
try {
|
|
398
337
|
this.#transport?.sendEvent(event);
|
|
399
338
|
this.#eventCount += 1;
|
|
400
339
|
this.#overlay?.update({ connected: true, events: this.#eventCount });
|
|
401
|
-
// On a route change, re-scope the HUD's replay-flow chips to the page we're now on.
|
|
402
340
|
if (type === EventType.ROUTE_CHANGE)
|
|
403
341
|
this.#presenter?.refilterFlows();
|
|
404
342
|
}
|
|
405
343
|
catch {
|
|
406
|
-
/* observation is best-effort; the host app's control flow is not */
|
|
407
344
|
}
|
|
408
345
|
};
|
|
409
|
-
/**
|
|
410
|
-
* Resolve the app's redaction config into the ambient policy, and remember the part of it that
|
|
411
|
-
* travels. A config of `undefined` is not "no policy" - it is the DEFAULT policy, installed
|
|
412
|
-
* explicitly so a second connect() in the same page (HMR, a re-mount) cannot inherit a rule the
|
|
413
|
-
* previous one set.
|
|
414
|
-
*/
|
|
415
346
|
#applyRedaction(config) {
|
|
416
347
|
setActiveRedactionPolicy(buildRedactionPolicy(config, nativeWarn));
|
|
417
348
|
this.#redactKeys = wireRedactionKeys(config);
|
|
@@ -427,42 +358,48 @@ export class Reticle {
|
|
|
427
358
|
adapters: adapterNames(),
|
|
428
359
|
...(this.#token === undefined ? {} : { token: this.#token }),
|
|
429
360
|
hasCapabilities: hasCapabilities(),
|
|
430
|
-
|
|
361
|
+
channels: declaredChannels(),
|
|
362
|
+
platform: declaredPlatform(),
|
|
363
|
+
commands: declaredCommands(),
|
|
364
|
+
captureBodies: this.#captureBodies,
|
|
365
|
+
...(this.#sourceMapping === undefined ? {} : { sourceMapping: this.#sourceMapping }),
|
|
431
366
|
...(this.#sdkVersion === undefined ? {} : { sdkVersion: this.#sdkVersion }),
|
|
432
|
-
// Always present: derived from THIS build's core, so it needs no build plugin to supply it.
|
|
433
|
-
// It is the half of the skew check that works on a hand-wired connect.
|
|
434
367
|
contract: CONTRACT_FINGERPRINT,
|
|
368
|
+
contractParts: {
|
|
369
|
+
commands: [...CONTRACT_PARTS.commands],
|
|
370
|
+
events: [...CONTRACT_PARTS.events],
|
|
371
|
+
actions: [...CONTRACT_PARTS.actions],
|
|
372
|
+
},
|
|
435
373
|
...(0 === this.#redactKeys.length ? {} : { redactKeys: this.#redactKeys }),
|
|
436
374
|
};
|
|
437
375
|
}
|
|
438
376
|
async #handleCommand(command) {
|
|
439
|
-
// NARRATE: the agent tells the human what it's about to do / decide (presenter HUD).
|
|
440
377
|
if (command.name === ReticleCommand.NARRATE) {
|
|
441
|
-
this.#presenter?.sessionStart();
|
|
378
|
+
this.#presenter?.sessionStart();
|
|
442
379
|
this.#presenter?.narrate(str(command.args['text']), str(command.args['level'], 'info'));
|
|
443
380
|
return { ok: true, result: { shown: this.#presenter !== undefined } };
|
|
444
381
|
}
|
|
445
|
-
// SESSION_CONFIG: the agent tunes the session for the app (currently the idle-end window).
|
|
446
382
|
if (command.name === ReticleCommand.SESSION_CONFIG) {
|
|
447
383
|
const idleEndMs = command.args['idleEndMs'];
|
|
448
384
|
if ('number' === typeof idleEndMs)
|
|
449
385
|
this.#presenter?.setIdleEndMs(idleEndMs);
|
|
450
386
|
return { ok: true, result: { applied: this.#presenter !== undefined, idleEndMs } };
|
|
451
387
|
}
|
|
452
|
-
// Bridge → browser presenter pushes (PRESENTER state echo / FLOWS replay list). The presenter owns
|
|
453
|
-
// the parsing; here we only report whether a panel was mounted to apply it. setState-only, so a
|
|
454
|
-
// PRESENTER echo of a HUMAN_CONTROL can't loop back into a re-emit.
|
|
455
388
|
if (command.name === ReticleCommand.PRESENTER ||
|
|
456
389
|
command.name === ReticleCommand.FLOWS ||
|
|
457
390
|
command.name === ReticleCommand.IMPACT) {
|
|
458
|
-
this.#presenter
|
|
459
|
-
|
|
391
|
+
if (this.#presenter === undefined) {
|
|
392
|
+
this.#pendingPushes.set(command.name, command);
|
|
393
|
+
return { ok: true, result: { applied: false, buffered: true } };
|
|
394
|
+
}
|
|
395
|
+
this.#presenter.handlePush(command);
|
|
396
|
+
return { ok: true, result: { applied: true } };
|
|
460
397
|
}
|
|
461
398
|
const handler = this.#registry.get(command.name);
|
|
462
399
|
if (handler === undefined) {
|
|
463
400
|
return { ok: false, error: `unknown command '${command.name}'` };
|
|
464
401
|
}
|
|
465
|
-
this.#presenter?.sessionStart();
|
|
402
|
+
this.#presenter?.sessionStart();
|
|
466
403
|
await this.#presentBefore(command);
|
|
467
404
|
try {
|
|
468
405
|
const result = await handler(command.args);
|
|
@@ -478,12 +415,11 @@ export class Reticle {
|
|
|
478
415
|
this.#presenter?.scheduleIdle();
|
|
479
416
|
}
|
|
480
417
|
}
|
|
481
|
-
/** Drive the presenter (cursor/effects/status) before the real action runs. */
|
|
482
418
|
async #presentBefore(command) {
|
|
483
419
|
const p = this.#presenter;
|
|
484
420
|
if (p === undefined)
|
|
485
421
|
return;
|
|
486
|
-
p.setMode(modeForCommand(command.name));
|
|
422
|
+
p.setMode(modeForCommand(command.name));
|
|
487
423
|
this.#actHandle = undefined;
|
|
488
424
|
if (command.name === ReticleCommand.ACT) {
|
|
489
425
|
const ref = str(command.args['ref']);
|
|
@@ -497,7 +433,6 @@ export class Reticle {
|
|
|
497
433
|
const s = step;
|
|
498
434
|
const ref = str(s.ref);
|
|
499
435
|
const label = refLabel(ref);
|
|
500
|
-
// one log row per step; the last handle carries the sequence outcome glyph
|
|
501
436
|
this.#actHandle = p.log(LOG_KIND.ACT, `${actionVerb(str(s.action))} ${label}`);
|
|
502
437
|
await p.beforeAct(ref, str(s.action), label);
|
|
503
438
|
}
|