@reticlehq/server 3.8.0 → 3.8.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/SKILL.md +2 -2
- package/dist/command/cli/answers/gate-hook-message.d.ts +2 -0
- package/dist/command/cli/answers/gate-hook-message.js +5 -1
- package/dist/command/cli/auth/cloud-session.d.ts +1 -19
- package/dist/command/cli/auth/device-flow.d.ts +3 -37
- package/dist/command/cli/cli-doctor.js +30 -23
- package/dist/command/cli/cli-flow-commands.js +7 -1
- package/dist/command/cli/cli-parse-serve.d.ts +24 -0
- package/dist/command/cli/cli-parse-serve.js +124 -0
- package/dist/command/cli/cli-parse.d.ts +1 -10
- package/dist/command/cli/cli-parse.js +62 -121
- package/dist/command/cli/cli-usage.js +5 -2
- package/dist/command/cli/cloud-link.d.ts +1 -20
- package/dist/command/cli/doctor/doctor-agent-link.d.ts +23 -0
- package/dist/command/cli/doctor/doctor-agent-link.js +28 -0
- package/dist/command/cli/ports/port-holder.js +6 -1
- package/dist/command/cli/project-name.d.ts +1 -7
- package/dist/command/cli/project-name.js +1 -0
- package/dist/command/daemon/daemon.d.ts +6 -0
- package/dist/command/daemon/daemon.js +52 -2
- package/dist/command/daemon/win32-escape.d.ts +32 -0
- package/dist/command/daemon/win32-escape.js +119 -0
- package/dist/command/setup/bringup/dev-server-files.d.ts +1 -9
- package/dist/command/setup/bringup/dev-server-wait.js +5 -1
- package/dist/command/version/version-skew.d.ts +2 -0
- package/dist/command/version/version-skew.js +7 -3
- package/dist/features/crawl/crawl-tools.js +2 -2
- package/dist/features/exhaust/ledger.d.ts +15 -96
- package/dist/features/exhaust/ledger.js +3 -3
- package/dist/features/license/license.d.ts +2 -14
- package/dist/hooks/hook-commands.js +19 -9
- package/dist/judgement/capsule/capsule-store.d.ts +2 -39
- package/dist/language/flows/annotate-tools.js +1 -1
- package/dist/language/flows/fields/flow-secret-field.js +1 -1
- package/dist/language/flows/flow-expect-grammar.js +14 -6
- package/dist/language/flows/flow-lease.d.ts +27 -0
- package/dist/language/flows/flow-lease.js +59 -0
- package/dist/language/flows/flow-replay-run.d.ts +1 -87
- package/dist/language/flows/flow-replay-run.js +88 -283
- package/dist/language/flows/flow-replayable.d.ts +1 -1
- package/dist/language/flows/flow-start-path.d.ts +108 -0
- package/dist/language/flows/flow-start-path.js +313 -0
- package/dist/language/flows/flow-tools.d.ts +2 -23
- package/dist/language/flows/flow-tools.js +34 -66
- package/dist/language/flows/recording/flake.d.ts +3 -27
- package/dist/language/flows/recording/flake.js +1 -1
- package/dist/language/flows/stores/assertion-tiers-store.js +1 -1
- package/dist/machine/git-tracks.js +1 -0
- package/dist/memory/cloud/cloud-sync.d.ts +2 -24
- package/dist/memory/intent/intent-shard.d.ts +41 -266
- package/dist/memory/journal/envelope-store.js +1 -1
- package/dist/memory/journal/envelope.d.ts +6 -154
- package/dist/memory/journal/on-disk/ambient-file.d.ts +1 -7
- package/dist/memory/journal/on-disk/ambient-file.js +1 -1
- package/dist/memory/journal/self-observation.js +3 -1
- package/dist/memory/journal/session-journal.js +39 -20
- package/dist/portal/input/drive-error.d.ts +6 -0
- package/dist/portal/input/drive-error.js +9 -0
- package/dist/portal/input/real-input-press.d.ts +34 -0
- package/dist/portal/input/real-input-press.js +150 -0
- package/dist/portal/input/real-input.d.ts +6 -23
- package/dist/portal/input/real-input.js +95 -192
- package/dist/portal/input/viewport-tools.d.ts +19 -15
- package/dist/portal/pool/browser-pool.d.ts +5 -55
- package/dist/portal/pool/browser-pool.js +121 -100
- package/dist/portal/pool/pool-lease.d.ts +128 -0
- package/dist/portal/pool/pool-lease.js +49 -0
- package/dist/portal/production/production-activation.d.ts +5 -27
- package/dist/portal/session/control-envelope.d.ts +1 -1
- package/dist/portal/session/zero-install-first-call.d.ts +3 -0
- package/dist/portal/session/zero-install-first-call.js +33 -12
- package/dist/surface/mcp/mcp.d.ts +3 -3
- package/dist/surface/mcp/mcp.js +4 -6
- package/dist/surface/tools/act/act-danger.js +32 -17
- package/dist/surface/tools/act/durable.js +1 -1
- package/dist/surface/tools/act-sequence-tool.js +3 -3
- package/dist/surface/tools/act-tools.js +15 -16
- package/dist/surface/tools/agent/compact-envelope.d.ts +2 -2
- package/dist/surface/tools/coverage-tools.js +1 -1
- package/dist/surface/tools/dynamic-tools.d.ts +8 -0
- package/dist/surface/tools/dynamic-tools.js +36 -16
- package/dist/surface/tools/empty-session-list.d.ts +5 -29
- package/dist/surface/tools/feedback-tools.js +1 -1
- package/dist/surface/tools/gap-novelty.d.ts +1 -1
- package/dist/surface/tools/gaps/capability-absences.js +4 -3
- package/dist/surface/tools/harness-gate.d.ts +1 -1
- package/dist/surface/tools/harness-toolset.js +3 -4
- package/dist/surface/tools/lease-permissions.d.ts +1 -1
- package/dist/surface/tools/lease-readiness.d.ts +23 -1
- package/dist/surface/tools/lease-readiness.js +75 -9
- package/dist/surface/tools/lease-tools.d.ts +1 -52
- package/dist/surface/tools/lease-tools.js +79 -142
- package/dist/surface/tools/observe-tools.js +3 -1
- package/dist/surface/tools/read-tools.js +5 -5
- package/dist/surface/tools/real-input-attempt.js +70 -31
- package/dist/surface/tools/session-ports.d.ts +57 -0
- package/dist/surface/tools/session-ports.js +102 -0
- package/dist/surface/tools/tool-kit.d.ts +5 -3
- package/dist/surface/tools/tools.js +1 -1
- package/dist/telemetry/daemon-telemetry.d.ts +3 -1
- package/dist/telemetry/daemon-telemetry.js +9 -2
- package/dist/telemetry/telemetry.d.ts +3 -1
- package/dist/telemetry/telemetry.js +2 -0
- package/docs/agent-cheatsheet.md +12 -0
- package/docs/cli/gate.mdx +1 -1
- package/docs/cli/verify.mdx +8 -1
- package/docs/first-drive.md +7 -0
- package/docs/multi-agent-testing.md +2 -0
- package/docs/packages/react.mdx +14 -2
- package/docs/packages/vite-plugin.mdx +1 -0
- package/docs/predicates.mdx +6 -0
- package/docs/skill.md +3 -3
- package/docs/troubleshooting.mdx +10 -0
- package/docs/usage.md +1 -1
- package/package.json +8 -9
|
@@ -6,7 +6,7 @@ import type { ToolDeps } from './tool-kit.js';
|
|
|
6
6
|
export declare const EMPTY_SESSION_LIST_OUTPUT: {
|
|
7
7
|
why: z.ZodOptional<z.ZodString>;
|
|
8
8
|
next_action: z.ZodOptional<z.ZodObject<{
|
|
9
|
-
action: z.
|
|
9
|
+
action: z.ZodEnum<{
|
|
10
10
|
readonly START_DEV_SERVER: "start_dev_server";
|
|
11
11
|
readonly RUN_INIT: "run_init";
|
|
12
12
|
readonly OPEN_APP: "open_app";
|
|
@@ -16,44 +16,20 @@ export declare const EMPTY_SESSION_LIST_OUTPUT: {
|
|
|
16
16
|
command: z.ZodOptional<z.ZodString>;
|
|
17
17
|
port: z.ZodOptional<z.ZodNumber>;
|
|
18
18
|
reason: z.ZodString;
|
|
19
|
-
},
|
|
20
|
-
action: "start_dev_server" | "run_init" | "open_app" | "reopen_app" | "daemon_split";
|
|
21
|
-
reason: string;
|
|
22
|
-
port?: number | undefined;
|
|
23
|
-
command?: string | undefined;
|
|
24
|
-
}, {
|
|
25
|
-
action: "start_dev_server" | "run_init" | "open_app" | "reopen_app" | "daemon_split";
|
|
26
|
-
reason: string;
|
|
27
|
-
port?: number | undefined;
|
|
28
|
-
command?: string | undefined;
|
|
29
|
-
}>>;
|
|
19
|
+
}, z.core.$strip>>;
|
|
30
20
|
talk_to_us: z.ZodOptional<z.ZodString>;
|
|
31
21
|
next_step: z.ZodOptional<z.ZodObject<{
|
|
32
|
-
offered: z.
|
|
22
|
+
offered: z.ZodEnum<{
|
|
33
23
|
readonly LEASE: "lease";
|
|
34
24
|
readonly INIT_CONSENT: "init_consent";
|
|
35
25
|
}>;
|
|
36
26
|
lease: z.ZodOptional<z.ZodString>;
|
|
37
27
|
consent: z.ZodString;
|
|
38
|
-
},
|
|
39
|
-
offered: "lease" | "init_consent";
|
|
40
|
-
consent: string;
|
|
41
|
-
lease?: string | undefined;
|
|
42
|
-
}, {
|
|
43
|
-
offered: "lease" | "init_consent";
|
|
44
|
-
consent: string;
|
|
45
|
-
lease?: string | undefined;
|
|
46
|
-
}>>;
|
|
28
|
+
}, z.core.$strip>>;
|
|
47
29
|
lastKnown: z.ZodOptional<z.ZodObject<{
|
|
48
30
|
sessionId: z.ZodString;
|
|
49
31
|
url: z.ZodString;
|
|
50
|
-
},
|
|
51
|
-
sessionId: string;
|
|
52
|
-
url: string;
|
|
53
|
-
}, {
|
|
54
|
-
sessionId: string;
|
|
55
|
-
url: string;
|
|
56
|
-
}>>;
|
|
32
|
+
}, z.core.$strip>>;
|
|
57
33
|
};
|
|
58
34
|
/**
|
|
59
35
|
* The next step for the calling project when another project's tab is the one connected.
|
|
@@ -43,5 +43,5 @@ export declare function resetGapNovelty(): void;
|
|
|
43
43
|
* The output-schema entry for `engineBlindSpots`, shared by every verdict tool so they cannot word
|
|
44
44
|
* it two ways. See `EngineBlindSpotKind` in core.
|
|
45
45
|
*/
|
|
46
|
-
export declare const engineBlindSpotsOutput: z.ZodOptional<z.ZodArray<z.ZodUnknown
|
|
46
|
+
export declare const engineBlindSpotsOutput: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
47
47
|
export {};
|
|
@@ -27,9 +27,10 @@ export function capabilityAbsences(session) {
|
|
|
27
27
|
if (false === session.captureBodies) {
|
|
28
28
|
absences.push({
|
|
29
29
|
capability: 'network-bodies',
|
|
30
|
-
meaning: 'request and response bodies are not recorded
|
|
31
|
-
'
|
|
32
|
-
|
|
30
|
+
meaning: 'request bodies and successful response bodies are not recorded (a failed response keeps ' +
|
|
31
|
+
'its body by default), so an assertion that reads one cannot be answered here and will be ' +
|
|
32
|
+
'refused before it spends an action',
|
|
33
|
+
remedy: "pass captureNetworkBodies: true to connect() in this app's Reticle setup, then reload the page. " +
|
|
33
34
|
'Bodies can carry personal data, so this is off unless a project asks for it.',
|
|
34
35
|
});
|
|
35
36
|
}
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
* agent can reach.
|
|
9
9
|
*/
|
|
10
10
|
import { z } from 'zod';
|
|
11
|
-
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
12
11
|
import { AppRuntime, ConsequenceKind, NetInitiator, PredicateKind, ReticleTool, } from '@reticlehq/core';
|
|
13
12
|
import { TOOLS } from './tools.js';
|
|
14
13
|
import { runTool } from './invoke-tool.js';
|
|
@@ -98,8 +97,8 @@ const INJECTED_PARAMS = new Set(['sessionId']);
|
|
|
98
97
|
* wedge, it would manufacture one.
|
|
99
98
|
*/
|
|
100
99
|
const TOOL_TIMEOUT_MS = 300_000;
|
|
101
|
-
/** What the MCP SDK passes
|
|
102
|
-
const MCP_SCHEMA_OPTIONS = {
|
|
100
|
+
/** What the MCP SDK passes zod's own converter, restated so the two surfaces cannot drift apart. */
|
|
101
|
+
const MCP_SCHEMA_OPTIONS = { target: 'draft-7', io: 'input' };
|
|
103
102
|
/** Convert one tool's zod shape into the JSON Schema the model wire wants. */
|
|
104
103
|
function toHarnessTool(tool) {
|
|
105
104
|
const asked = Object.fromEntries(Object.entries(tool.inputSchema).filter(([name]) => !INJECTED_PARAMS.has(name)));
|
|
@@ -115,7 +114,7 @@ function toHarnessTool(tool) {
|
|
|
115
114
|
* argument that decides whether a drive proves anything. Definitions and refs are what every
|
|
116
115
|
* agent driving Reticle over MCP already reads.
|
|
117
116
|
*/
|
|
118
|
-
inputSchema:
|
|
117
|
+
inputSchema: z.toJSONSchema(z.object(asked), MCP_SCHEMA_OPTIONS),
|
|
119
118
|
};
|
|
120
119
|
}
|
|
121
120
|
/**
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { z } from 'zod';
|
|
10
10
|
import type { BrowserPool } from '../../portal/pool/browser-pool.js';
|
|
11
|
-
export declare const LEASE_PERMISSIONS_ARG: z.ZodOptional<z.ZodArray<z.ZodString
|
|
11
|
+
export declare const LEASE_PERMISSIONS_ARG: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
12
12
|
type PermissionPool = Pick<BrowserPool, 'notificationPermission'>;
|
|
13
13
|
/** The validated list, undefined when the caller passed none, or a refusal naming the right shape. */
|
|
14
14
|
export declare function parseLeasePermissions(raw: unknown): readonly string[] | undefined;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Is a leased tab usable right now? The checks `reticle_lease` runs before it may say `ready: true`.
|
|
3
3
|
* Presence in the sessions map is not enough: the tab has to answer, and it has to be visible.
|
|
4
|
-
* The wait for the tab to connect is here too, with the zero-install reader for a page that stays silent
|
|
4
|
+
* The wait for the tab to connect is here too, with the zero-install reader for a page that stays silent,
|
|
5
|
+
* and the move that takes a reused lease to the page it was asked for.
|
|
5
6
|
*/
|
|
6
7
|
import { LeaseNotReadyReason } from '@reticlehq/core';
|
|
7
8
|
/** The narrow slice of a session the probe needs. Anything that quacks like this works. */
|
|
@@ -16,6 +17,11 @@ interface ProbeableSession {
|
|
|
16
17
|
* default `false`. The pool foregrounding the page is what covers the gap; this catches the rest.
|
|
17
18
|
*/
|
|
18
19
|
export declare function tabHidden(session: unknown): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Why a lease that connected is not ready: `failed` when it did not answer, `tab_hidden` when it
|
|
22
|
+
* answers from a hidden tab, nothing when it is ready.
|
|
23
|
+
*/
|
|
24
|
+
export declare function notReadyReason(answered: boolean, session: unknown, failed?: LeaseNotReadyReason): LeaseNotReadyReason | undefined;
|
|
19
25
|
/**
|
|
20
26
|
* Does this tab still answer?
|
|
21
27
|
*
|
|
@@ -68,4 +74,20 @@ export interface LeaseReaderFailure {
|
|
|
68
74
|
reason: LeaseNotReadyReason;
|
|
69
75
|
cause: string;
|
|
70
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Move a reused lease to the page it was asked for (#1667).
|
|
79
|
+
*
|
|
80
|
+
* Reuse hands back the tab wherever the last caller left it. Answering with the requested url
|
|
81
|
+
* while the tab stayed elsewhere sent the next read to another page. So the tab goes there, and
|
|
82
|
+
* the answer waits for its SDK to come back on that page, as a minted lease's does.
|
|
83
|
+
*
|
|
84
|
+
* `undefined` when there is nothing to move: the tab is already there, or its url is unknown.
|
|
85
|
+
* Otherwise `navigated` says whether the tab got there, `url` is the page it is on, and `arrived`
|
|
86
|
+
* whether its SDK connected there within the wait.
|
|
87
|
+
*/
|
|
88
|
+
export declare function moveReusedLease(tabUrl: () => string | undefined, requested: string, goto: () => Promise<boolean>, now: () => number): Promise<{
|
|
89
|
+
navigated: boolean;
|
|
90
|
+
url: string;
|
|
91
|
+
arrived: boolean;
|
|
92
|
+
} | undefined>;
|
|
71
93
|
export {};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Is a leased tab usable right now? The checks `reticle_lease` runs before it may say `ready: true`.
|
|
3
3
|
* Presence in the sessions map is not enough: the tab has to answer, and it has to be visible.
|
|
4
|
-
* The wait for the tab to connect is here too, with the zero-install reader for a page that stays silent
|
|
4
|
+
* The wait for the tab to connect is here too, with the zero-install reader for a page that stays silent,
|
|
5
|
+
* and the move that takes a reused lease to the page it was asked for.
|
|
5
6
|
*/
|
|
6
|
-
import { LeaseNotReadyReason, ReticleCommand } from '@reticlehq/core';
|
|
7
|
+
import { LeaseNotReadyReason, ReticleCommand, RETICLE_URL_PARAM } from '@reticlehq/core';
|
|
7
8
|
import { NotHtmlDocumentError } from '../../portal/pool/zero-install.js';
|
|
8
9
|
/**
|
|
9
10
|
* How long a liveness probe waits for the tab to say anything at all.
|
|
@@ -25,6 +26,16 @@ export function tabHidden(session) {
|
|
|
25
26
|
'object' === typeof session &&
|
|
26
27
|
true === session.info?.().hidden);
|
|
27
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Why a lease that connected is not ready: `failed` when it did not answer, `tab_hidden` when it
|
|
31
|
+
* answers from a hidden tab, nothing when it is ready.
|
|
32
|
+
*/
|
|
33
|
+
export function notReadyReason(answered, session, failed = LeaseNotReadyReason.SDK_STOPPED_ANSWERING) {
|
|
34
|
+
if (!answered) {
|
|
35
|
+
return failed;
|
|
36
|
+
}
|
|
37
|
+
return tabHidden(session) ? LeaseNotReadyReason.TAB_HIDDEN : undefined;
|
|
38
|
+
}
|
|
28
39
|
/**
|
|
29
40
|
* Does this tab still answer?
|
|
30
41
|
*
|
|
@@ -47,8 +58,9 @@ export function tabHidden(session) {
|
|
|
47
58
|
*/
|
|
48
59
|
export async function probeLeaseAlive(session, timeoutMs = LEASE_PROBE_TIMEOUT_MS) {
|
|
49
60
|
const send = session?.command;
|
|
50
|
-
if (send === undefined)
|
|
61
|
+
if (send === undefined) {
|
|
51
62
|
return true;
|
|
63
|
+
}
|
|
52
64
|
try {
|
|
53
65
|
await send.call(session, ReticleCommand.CAPABILITIES, {}, timeoutMs);
|
|
54
66
|
return true;
|
|
@@ -70,8 +82,9 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
70
82
|
*/
|
|
71
83
|
export async function waitForLeasedSession(isConnected, sleeper = sleep, attempts = LEASE_READY_ATTEMPTS, pollMs = LEASE_READY_POLL_MS) {
|
|
72
84
|
for (let i = 0; i < attempts; i++) {
|
|
73
|
-
if (isConnected())
|
|
85
|
+
if (isConnected()) {
|
|
74
86
|
return true;
|
|
87
|
+
}
|
|
75
88
|
await sleeper(pollMs);
|
|
76
89
|
}
|
|
77
90
|
return isConnected();
|
|
@@ -86,8 +99,9 @@ export async function waitForLeasedSession(isConnected, sleeper = sleep, attempt
|
|
|
86
99
|
* that stayed silent through the whole readiness wait is the one that gets a reader.
|
|
87
100
|
*/
|
|
88
101
|
export async function connectOrInject(lease, isConnected, wait = waitForLeasedSession, graceRounds = LEASE_LOADING_GRACE_ROUNDS) {
|
|
89
|
-
if (await wait(isConnected))
|
|
102
|
+
if (await wait(isConnected)) {
|
|
90
103
|
return { ready: true, zeroInstall: false };
|
|
104
|
+
}
|
|
91
105
|
let injected;
|
|
92
106
|
try {
|
|
93
107
|
injected = true === (await lease.injectReader?.());
|
|
@@ -97,20 +111,24 @@ export async function connectOrInject(lease, isConnected, wait = waitForLeasedSe
|
|
|
97
111
|
await lease.release?.().catch(() => undefined);
|
|
98
112
|
return { ready: false, zeroInstall: false, readerFailure: readerFailureOf(err) };
|
|
99
113
|
}
|
|
100
|
-
if (injected && (await wait(isConnected)))
|
|
114
|
+
if (injected && (await wait(isConnected))) {
|
|
101
115
|
return { ready: true, zeroInstall: true };
|
|
116
|
+
}
|
|
102
117
|
// A slow first dev compile can hold the document past both waits, and its app connects a moment
|
|
103
118
|
// later (#1555). Keep waiting while the page itself says it is still loading, bounded so a page
|
|
104
119
|
// that never finishes cannot hold the acquire forever.
|
|
105
120
|
for (let round = 0; round < graceRounds; round++) {
|
|
106
|
-
if (!(await pageStillLoading(lease)))
|
|
121
|
+
if (!(await pageStillLoading(lease))) {
|
|
107
122
|
break;
|
|
108
|
-
|
|
123
|
+
}
|
|
124
|
+
if (await wait(isConnected)) {
|
|
109
125
|
return { ready: true, zeroInstall: injected };
|
|
126
|
+
}
|
|
110
127
|
}
|
|
111
128
|
// Re-check right before answering: a connect that landed during the last loading probe counts.
|
|
112
|
-
if (isConnected())
|
|
129
|
+
if (isConnected()) {
|
|
113
130
|
return { ready: true, zeroInstall: injected };
|
|
131
|
+
}
|
|
114
132
|
return (await pageStillLoading(lease))
|
|
115
133
|
? { ready: false, zeroInstall: false, stillLoading: true }
|
|
116
134
|
: { ready: false, zeroInstall: false };
|
|
@@ -137,3 +155,51 @@ function readerFailureOf(err) {
|
|
|
137
155
|
cause,
|
|
138
156
|
};
|
|
139
157
|
}
|
|
158
|
+
/** How long a moved lease's SDK gets to come back on the new page: the mint path's wait. */
|
|
159
|
+
const REUSED_ARRIVAL_MS = LEASE_READY_ATTEMPTS * LEASE_READY_POLL_MS;
|
|
160
|
+
/** A URL as a page: without Reticle's own params or the fragment. Undefined when it is not a URL. */
|
|
161
|
+
function pageOf(url) {
|
|
162
|
+
if (url === undefined) {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
const u = new URL(url);
|
|
167
|
+
for (const param of Object.values(RETICLE_URL_PARAM)) {
|
|
168
|
+
u.searchParams.delete(param);
|
|
169
|
+
}
|
|
170
|
+
u.hash = '';
|
|
171
|
+
return u.href;
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Move a reused lease to the page it was asked for (#1667).
|
|
179
|
+
*
|
|
180
|
+
* Reuse hands back the tab wherever the last caller left it. Answering with the requested url
|
|
181
|
+
* while the tab stayed elsewhere sent the next read to another page. So the tab goes there, and
|
|
182
|
+
* the answer waits for its SDK to come back on that page, as a minted lease's does.
|
|
183
|
+
*
|
|
184
|
+
* `undefined` when there is nothing to move: the tab is already there, or its url is unknown.
|
|
185
|
+
* Otherwise `navigated` says whether the tab got there, `url` is the page it is on, and `arrived`
|
|
186
|
+
* whether its SDK connected there within the wait.
|
|
187
|
+
*/
|
|
188
|
+
export async function moveReusedLease(tabUrl, requested, goto, now) {
|
|
189
|
+
const from = tabUrl();
|
|
190
|
+
const target = pageOf(requested);
|
|
191
|
+
if (pageOf(from) === undefined || target === undefined || pageOf(from) === target) {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
const went = await Promise.resolve()
|
|
195
|
+
.then(goto)
|
|
196
|
+
.catch(() => false);
|
|
197
|
+
if (!went) {
|
|
198
|
+
return { navigated: false, url: from ?? requested, arrived: true };
|
|
199
|
+
}
|
|
200
|
+
const start = now();
|
|
201
|
+
while (pageOf(tabUrl()) !== target && now() - start < REUSED_ARRIVAL_MS) {
|
|
202
|
+
await sleep(LEASE_READY_POLL_MS);
|
|
203
|
+
}
|
|
204
|
+
return { navigated: true, url: requested, arrived: pageOf(tabUrl()) === target };
|
|
205
|
+
}
|
|
@@ -9,14 +9,11 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Attach-only: the pool drives a browser against an already-running dev server — it never starts one.
|
|
11
11
|
*/
|
|
12
|
-
import type { RealInputProvider } from '../../portal/input/real-input.js';
|
|
13
|
-
import { type StorageFixturePort } from '../../portal/input/storage-fixture.js';
|
|
14
|
-
import { type NetworkMutationPort } from '../../portal/input/network-mutation.js';
|
|
15
|
-
import type { Perturbation } from '@reticlehq/core';
|
|
16
12
|
import { type SeedStorage } from '@reticlehq/core';
|
|
17
13
|
import { HudVisibility } from '@reticlehq/core';
|
|
18
14
|
import { type ToolDef, type ToolDeps } from './tool-kit.js';
|
|
19
15
|
import { type BrowserPool } from '../../portal/pool/browser-pool.js';
|
|
16
|
+
export { sessionMutationPort, sessionPerturbationPort, suiteFixturePort, suiteFixtureSeed, } from './session-ports.js';
|
|
20
17
|
/**
|
|
21
18
|
* Append Reticle identity params (__reticle_session, optional __reticle_project) to a URL so the app's own SDK
|
|
22
19
|
* adopts them on connect. Pure; falls back to plain concatenation if the URL can't be parsed.
|
|
@@ -101,51 +98,3 @@ export declare const LEASE_ACQUIRE_TOOL: ToolDef;
|
|
|
101
98
|
*/
|
|
102
99
|
export declare function releaseLease(deps: ToolDeps, pool: BrowserPool, sessionId: string): Promise<number>;
|
|
103
100
|
export declare const LEASE_TOOLS: ToolDef[];
|
|
104
|
-
/**
|
|
105
|
-
* The state a suite should boot its flows from: whatever the agent is already sitting in.
|
|
106
|
-
*
|
|
107
|
-
* This is the join the fixture work was building toward. A suite's flows each start from cold, so
|
|
108
|
-
* fifty of them prove the login works fifty times — and the flow that LOGS OUT leaves every flow
|
|
109
|
-
* after it signed out, which no navigation repairs, because the problem is not where the subject is
|
|
110
|
-
* but what it holds. A seed is applied to an isolated context BEFORE the first navigation, which is
|
|
111
|
-
* exactly and only when a fixture can work.
|
|
112
|
-
*
|
|
113
|
-
* Best-effort, deliberately. A fixture is an OPTIMISATION over running from cold, and running from
|
|
114
|
-
* cold is correct: a suite that refused to start because a cookie jar could not be read would have
|
|
115
|
-
* traded a slow answer for no answer. Every "no" here — no provider, no storage support, nothing
|
|
116
|
-
* driven, a browser that threw, nothing worth seeding — produces the same honest `undefined`, and
|
|
117
|
-
* the suite runs exactly as it did before any of this existed.
|
|
118
|
-
*/
|
|
119
|
-
export declare function suiteFixtureSeed(realInput: RealInputProvider | undefined, appUrl: string): Promise<SeedStorage | undefined>;
|
|
120
|
-
/**
|
|
121
|
-
* A way to make this session's page SLOW on purpose, or nothing.
|
|
122
|
-
*
|
|
123
|
-
* The third of these seams, for the same reason as the other two: a feature reaching into the input
|
|
124
|
-
* layer directly is a feature depending on a driver, and the boundary guard asks about it.
|
|
125
|
-
*
|
|
126
|
-
* Installs delays and NOTHING else. A rule that carried a status or a body would substitute a
|
|
127
|
-
* response the server never sent, and an app raced against invented data produces invented races —
|
|
128
|
-
* a fabricated defect handed to somebody at 3am, which is worse than finding nothing.
|
|
129
|
-
*/
|
|
130
|
-
export declare function sessionPerturbationPort(realInput: RealInputProvider | undefined, appUrl: string | undefined): Promise<{
|
|
131
|
-
slow(rules: readonly Perturbation[]): Promise<void>;
|
|
132
|
-
clear(): Promise<void>;
|
|
133
|
-
} | undefined>;
|
|
134
|
-
/**
|
|
135
|
-
* Save/restore for this session's page, or nothing.
|
|
136
|
-
*
|
|
137
|
-
* The same job as `suiteFixtureSeed` next door, stopping one step earlier: that one captures and
|
|
138
|
-
* converts, this hands back the PORT so a caller can re-apply what it captured later. It lives here
|
|
139
|
-
* for the reason written above — a feature reaching into the input layer directly is a feature
|
|
140
|
-
* depending on a driver, and the boundary guard asks about it. It asked.
|
|
141
|
-
*/
|
|
142
|
-
export declare function suiteFixturePort(realInput: RealInputProvider | undefined, appUrl: string | undefined): Promise<StorageFixturePort | undefined>;
|
|
143
|
-
/**
|
|
144
|
-
* A way to break this session's page, or nothing.
|
|
145
|
-
*
|
|
146
|
-
* Sits beside `suiteFixtureSeed` because it is the same shape of job: turning a provider capability
|
|
147
|
-
* into something a feature can use, on the one side of the graph that is allowed to know about both.
|
|
148
|
-
* A flow tool reaching into the input layer directly would be a feature depending on a driver, which
|
|
149
|
-
* is the reach the boundary guard exists to ask about.
|
|
150
|
-
*/
|
|
151
|
-
export declare function sessionMutationPort(realInput: RealInputProvider | undefined, appUrl: string | undefined): Promise<NetworkMutationPort | undefined>;
|