@reticlehq/server 3.8.2 → 3.9.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/LICENSE +201 -104
- package/NOTICE +5 -0
- package/README.md +1 -1
- package/bin/launch.js +13 -0
- package/bin/package.json +3 -0
- package/bin/reticle.js +66 -8
- package/dist/chromium-launch-options.d.ts +25 -0
- package/dist/chromium-launch-options.js +21 -1
- package/dist/command/cli/adhoc-verdict.js +34 -7
- package/dist/command/cli/answers/gate-exit.d.ts +6 -2
- package/dist/command/cli/answers/gate-exit.js +12 -2
- package/dist/command/cli/ci-hint.d.ts +44 -0
- package/dist/command/cli/ci-hint.js +72 -0
- package/dist/command/cli/cli-doctor.d.ts +6 -1
- package/dist/command/cli/cli-doctor.js +45 -4
- package/dist/command/cli/cli-flow-commands.d.ts +7 -1
- package/dist/command/cli/cli-flow-commands.js +39 -17
- package/dist/command/cli/cli-kill.d.ts +21 -0
- package/dist/command/cli/cli-kill.js +46 -3
- package/dist/command/cli/cli-parse.d.ts +5 -2
- package/dist/command/cli/cli-parse.js +10 -5
- package/dist/command/cli/cli-usage.js +3 -2
- package/dist/command/cli/cli-verify.d.ts +6 -1
- package/dist/command/cli/cli-verify.js +32 -13
- package/dist/command/cli/doctor/browser/chromium-hint.d.ts +6 -1
- package/dist/command/cli/doctor/browser/chromium-hint.js +11 -5
- package/dist/command/cli/doctor/doctor-fix.d.ts +44 -0
- package/dist/command/cli/doctor/doctor-fix.js +89 -0
- package/dist/command/cli/doctor/doctor-launcher-line.d.ts +23 -0
- package/dist/command/cli/doctor/doctor-launcher-line.js +60 -0
- package/dist/command/cli/doctor/doctor-rows.d.ts +4 -0
- package/dist/command/cli/doctor/doctor-rows.js +4 -0
- package/dist/command/cli/doctor-repairs.d.ts +64 -0
- package/dist/command/cli/doctor-repairs.js +214 -0
- package/dist/command/cli/drive/drive-command.js +3 -7
- package/dist/command/cli/first-use-checks.d.ts +41 -0
- package/dist/command/cli/first-use-checks.js +127 -0
- package/dist/command/cli/first-verdict-node.d.ts +3 -0
- package/dist/command/cli/first-verdict-node.js +189 -0
- package/dist/command/cli/first-verdict.d.ts +126 -0
- package/dist/command/cli/first-verdict.js +267 -0
- package/dist/command/cli/launch/sdk-version-match.d.ts +3 -2
- package/dist/command/cli/launch/sdk-version-match.js +3 -2
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +6 -5
- package/dist/command/cli/mcp-command.js +13 -4
- package/dist/command/cli/ports/port-holder.d.ts +2 -0
- package/dist/command/cli/ports/port-holder.js +11 -3
- package/dist/command/cli/setup-mcp-cli.d.ts +27 -1
- package/dist/command/cli/setup-mcp-cli.js +98 -23
- package/dist/command/cli/status/status-lines.d.ts +1 -0
- package/dist/command/cli/status/status-lines.js +50 -17
- package/dist/command/cli/status-fields.js +17 -3
- package/dist/command/cli/try-command.d.ts +7 -0
- package/dist/command/cli/try-command.js +11 -3
- package/dist/command/cli.js +70 -33
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +3 -1
- package/dist/command/daemon/binding/daemon-status-probe.js +4 -2
- package/dist/command/daemon/daemon-argv.d.ts +9 -0
- package/dist/command/daemon/daemon-argv.js +11 -0
- package/dist/command/daemon/daemon.d.ts +10 -3
- package/dist/command/daemon/daemon.js +17 -6
- package/dist/command/daemon/lifetime/daemon-exit.d.ts +24 -0
- package/dist/command/daemon/lifetime/daemon-exit.js +39 -0
- package/dist/command/daemon/lifetime/death-cause.d.ts +15 -0
- package/dist/command/daemon/lifetime/death-cause.js +130 -0
- package/dist/command/daemon/lifetime/heartbeat.d.ts +7 -15
- package/dist/command/daemon/lifetime/heartbeat.js +4 -13
- package/dist/command/daemon/port-state.d.ts +14 -0
- package/dist/command/daemon/port-state.js +31 -0
- package/dist/command/setup/agent-configs.d.ts +2 -0
- package/dist/command/setup/agent-configs.js +42 -15
- package/dist/command/setup/agent-next-steps.d.ts +77 -0
- package/dist/command/setup/agent-next-steps.js +284 -0
- package/dist/command/setup/agent-writer.js +11 -6
- package/dist/command/setup/init/init-host.js +2 -0
- package/dist/command/setup/init/init-runtime.d.ts +6 -1
- package/dist/command/setup/init/init-runtime.js +57 -21
- package/dist/command/setup/mcp-launcher.d.ts +3 -0
- package/dist/command/setup/mcp-launcher.js +11 -0
- package/dist/command/setup/setup-command.js +30 -12
- package/dist/command/setup/setup-mcp.d.ts +8 -0
- package/dist/command/setup/setup-mcp.js +47 -20
- package/dist/command/setup/start-dev-server.d.ts +8 -0
- package/dist/command/setup/start-dev-server.js +43 -0
- package/dist/command/version/identity/server-version.d.ts +2 -0
- package/dist/command/version/identity/server-version.js +2 -0
- package/dist/command/version/version-skew.d.ts +7 -0
- package/dist/command/version/version-skew.js +13 -2
- package/dist/features/license/license.d.ts +11 -71
- package/dist/features/license/license.js +43 -136
- package/dist/features/visual/desktop-capture.d.ts +33 -0
- package/dist/features/visual/desktop-capture.js +94 -0
- package/dist/features/visual/step-shots.js +139 -35
- package/dist/features/visual/visual-tools.d.ts +0 -18
- package/dist/features/visual/visual-tools.js +2 -81
- package/dist/import-graph.js +5 -6
- package/dist/index.d.ts +3 -1
- package/dist/index.js +14 -5
- package/dist/judgement/runs/run-ledger.d.ts +7 -5
- package/dist/judgement/runs/run-ledger.js +98 -69
- package/dist/judgement/runs/verification-sync.d.ts +6 -3
- package/dist/judgement/runs/verification-sync.js +21 -10
- package/dist/language/flows/flow-store-for-session.js +7 -5
- package/dist/language/flows/recording/tape/recordings.d.ts +2 -13
- package/dist/language/flows/recording/tape/recordings.js +41 -44
- package/dist/language/flows/replay.d.ts +17 -7
- package/dist/language/flows/replay.js +95 -44
- package/dist/launch-chromium.d.ts +1 -0
- package/dist/launch-chromium.js +49 -12
- package/dist/memory/cloud/sync-screenshots.js +6 -1
- package/dist/portal/bridge/bridge.d.ts +13 -4
- package/dist/portal/bridge/bridge.js +10 -9
- package/dist/portal/input/network-mock.js +3 -2
- package/dist/portal/input/real-input.d.ts +15 -0
- package/dist/portal/input/real-input.js +59 -3
- package/dist/portal/input/viewport-tools.js +6 -3
- package/dist/portal/pool/browser-pool.d.ts +5 -1
- package/dist/portal/pool/browser-pool.js +32 -38
- package/dist/portal/pool/context-permissions.d.ts +20 -17
- package/dist/portal/pool/context-permissions.js +63 -1
- package/dist/portal/pool/csp-widen.d.ts +62 -0
- package/dist/portal/pool/csp-widen.js +132 -0
- package/dist/portal/pool/lease-page-error.d.ts +29 -0
- package/dist/portal/pool/lease-page-error.js +18 -0
- package/dist/portal/pool/page-error-journal.d.ts +57 -0
- package/dist/portal/pool/page-error-journal.js +181 -0
- package/dist/portal/pool/playwright-launcher.js +21 -8
- package/dist/portal/pool/pool-contract.d.ts +22 -0
- package/dist/portal/pool/pool-lease.d.ts +35 -2
- package/dist/portal/pool/pool-lease.js +23 -0
- package/dist/portal/pool/zero-install.js +69 -1
- package/dist/portal/session/dev-server/dev-command.d.ts +8 -0
- package/dist/portal/session/dev-server/dev-command.js +56 -8
- package/dist/portal/session/no-session-diagnosis.js +10 -3
- package/dist/portal/session/no-session-next-action.d.ts +12 -0
- package/dist/portal/session/no-session-next-action.js +44 -4
- package/dist/portal/session/no-session-watch.js +3 -1
- package/dist/portal/session/page-commands/undeclared-command.d.ts +9 -3
- package/dist/portal/session/page-commands/undeclared-command.js +16 -7
- package/dist/portal/session/recorded-gaps.d.ts +1 -1
- package/dist/portal/session/recorded-gaps.js +5 -2
- package/dist/portal/session/session-manager.js +40 -21
- package/dist/portal/session/session.js +4 -3
- package/dist/portal/session/zero-install-first-call.d.ts +20 -1
- package/dist/portal/session/zero-install-first-call.js +34 -2
- package/dist/start-wiring.d.ts +29 -2
- package/dist/start-wiring.js +73 -9
- package/dist/surface/mcp/mcp-proxy.js +10 -2
- package/dist/surface/mcp/mcp.js +5 -3
- package/dist/surface/mcp/peer/agents-connected.d.ts +6 -0
- package/dist/surface/mcp/peer/agents-connected.js +58 -0
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +17 -5
- package/dist/surface/tools/act/act-preflight.js +37 -17
- package/dist/surface/tools/act/act-target.js +3 -1
- package/dist/surface/tools/act/after-match.d.ts +7 -0
- package/dist/surface/tools/act/after-match.js +33 -0
- package/dist/surface/tools/act/resolve-target.d.ts +2 -0
- package/dist/surface/tools/act/resolve-target.js +35 -8
- package/dist/surface/tools/act/sequence-grade.d.ts +3 -1
- package/dist/surface/tools/act/sequence-grade.js +8 -1
- package/dist/surface/tools/act-sequence-tool.js +3 -0
- package/dist/surface/tools/act-tools.js +10 -10
- package/dist/surface/tools/assert/assert-verdict.js +28 -1
- package/dist/surface/tools/browser-tools.js +75 -7
- package/dist/surface/tools/dynamic-tools.js +2 -1
- package/dist/surface/tools/empty-session-list.d.ts +2 -0
- package/dist/surface/tools/empty-session-list.js +4 -2
- package/dist/surface/tools/error-recovery.js +43 -22
- package/dist/surface/tools/invoke-tool.js +27 -20
- package/dist/surface/tools/lease-hint.d.ts +7 -0
- package/dist/surface/tools/lease-hint.js +10 -3
- package/dist/surface/tools/lease-readiness.d.ts +21 -0
- package/dist/surface/tools/lease-readiness.js +47 -0
- package/dist/surface/tools/lease-seed.d.ts +2 -0
- package/dist/surface/tools/lease-seed.js +2 -1
- package/dist/surface/tools/lease-tools.js +43 -15
- package/dist/surface/tools/repeat-hint.d.ts +40 -0
- package/dist/surface/tools/repeat-hint.js +171 -0
- package/dist/surface/tools/run-report.d.ts +2 -2
- package/dist/surface/tools/run-report.js +27 -15
- package/dist/surface/tools/tool-kit.d.ts +12 -0
- package/dist/surface/tools/tool-kit.js +7 -0
- package/dist/surface/tools/tools.js +26 -13
- package/dist/telemetry/argument-shape.js +16 -7
- package/dist/telemetry/cli-telemetry.d.ts +10 -1
- package/dist/telemetry/cli-telemetry.js +15 -0
- package/dist/telemetry/init-telemetry.d.ts +2 -1
- package/dist/telemetry/init-telemetry.js +47 -2
- package/dist/telemetry/install-trace.js +23 -4
- package/dist/telemetry/onboarding-funnel.d.ts +19 -2
- package/dist/telemetry/onboarding-funnel.js +25 -3
- package/dist/telemetry/proxy-telemetry.d.ts +2 -1
- package/dist/telemetry/proxy-telemetry.js +2 -2
- package/dist/telemetry/session-metrics.d.ts +1 -1
- package/dist/telemetry/session-metrics.js +67 -27
- package/dist/telemetry/telemetry.d.ts +3 -1
- package/dist/telemetry/telemetry.js +60 -28
- package/dist/telemetry/tool-refused.d.ts +2 -2
- package/dist/telemetry/tool-refused.js +9 -2
- package/docs/architecture.md +3 -6
- package/docs/cli/doctor.mdx +41 -2
- package/docs/cli/help.mdx +4 -2
- package/docs/cli/license.mdx +4 -5
- package/docs/cli/setup.mdx +13 -2
- package/docs/cli/status.mdx +4 -0
- package/docs/cli.mdx +1 -1
- package/docs/deploy-checks.md +25 -0
- package/docs/desktop.mdx +1 -1
- package/docs/enterprise.md +15 -20
- package/docs/environment.mdx +1 -0
- package/docs/first-drive.md +1 -1
- package/docs/install-manual.mdx +16 -1
- package/docs/packages/engine.mdx +1 -1
- package/docs/packages/server.mdx +1 -1
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages/test.mdx +1 -1
- package/docs/packages.mdx +1 -1
- package/docs/platform-integration.md +1 -1
- package/docs/skill.md +1 -1
- package/docs/telemetry.md +4 -2
- package/package.json +13 -9
- package/LICENSE-ENTERPRISE +0 -37
- package/dist/features/ee/audit-log.d.ts +0 -14
- package/dist/features/ee/audit-log.js +0 -11
|
@@ -32,8 +32,9 @@ const ELISION = '… [elided] …';
|
|
|
32
32
|
*/
|
|
33
33
|
function capMessage(message) {
|
|
34
34
|
const max = TRANSPORT_LIMITS.MAX_ERROR_LENGTH;
|
|
35
|
-
if (max >= message.length)
|
|
35
|
+
if (max >= message.length) {
|
|
36
36
|
return message;
|
|
37
|
+
}
|
|
37
38
|
const keep = Math.floor((max - ELISION.length) / 2);
|
|
38
39
|
return `${message.slice(0, keep)}${ELISION}${message.slice(message.length - keep)}`;
|
|
39
40
|
}
|
|
@@ -83,8 +84,9 @@ function isUnissuedRefRefusal(message) {
|
|
|
83
84
|
}
|
|
84
85
|
/** A message that already carries its own concrete next action, so nothing should be appended. */
|
|
85
86
|
function isSelfRecovering(message) {
|
|
86
|
-
if (message.includes(SELF_RECOVERING_MARKER))
|
|
87
|
+
if (message.includes(SELF_RECOVERING_MARKER)) {
|
|
87
88
|
return true;
|
|
89
|
+
}
|
|
88
90
|
return SELF_RECOVERING.some((rule) => rule.match.test(message));
|
|
89
91
|
}
|
|
90
92
|
/** The recovery hints, named so they are not free strings and can be asserted in tests. */
|
|
@@ -142,8 +144,9 @@ export const RECOVERY = {
|
|
|
142
144
|
* carry: which of them to reach for, and that nothing was acted on.
|
|
143
145
|
*/
|
|
144
146
|
TARGET_MISSED: 'The call was valid — the selector matched nothing on the page RIGHT NOW, and nothing was ' +
|
|
145
|
-
'acted on.
|
|
146
|
-
'
|
|
147
|
+
'acted on. If the message lists the nearest targets and one is the element you meant, retry ' +
|
|
148
|
+
'with its ref. If not, take a reticle_snapshot to see what is actually there (a view may still ' +
|
|
149
|
+
'be rendering, or the control may be named differently), then retry with what it shows. This is ' +
|
|
147
150
|
'a miss, not a Reticle defect: there is nothing to report.',
|
|
148
151
|
/**
|
|
149
152
|
* The selector was underspecified, which is the caller's to fix and the same class as a miss.
|
|
@@ -464,13 +467,16 @@ const RULES = [
|
|
|
464
467
|
* saying a server is running and the recovery saying to go start one.
|
|
465
468
|
*/
|
|
466
469
|
export function recoveryFor(message) {
|
|
467
|
-
if (isUnissuedRefRefusal(message))
|
|
470
|
+
if (isUnissuedRefRefusal(message)) {
|
|
468
471
|
return RECOVERY.UNISSUED_REF;
|
|
469
|
-
|
|
472
|
+
}
|
|
473
|
+
if (isSelfRecovering(message)) {
|
|
470
474
|
return undefined;
|
|
475
|
+
}
|
|
471
476
|
for (const rule of RULES) {
|
|
472
|
-
if (rule.match.test(message))
|
|
477
|
+
if (rule.match.test(message)) {
|
|
473
478
|
return rule.hint;
|
|
479
|
+
}
|
|
474
480
|
}
|
|
475
481
|
return undefined;
|
|
476
482
|
}
|
|
@@ -483,25 +489,31 @@ export function recoveryFor(message) {
|
|
|
483
489
|
* this table rather than about anybody's app.
|
|
484
490
|
*/
|
|
485
491
|
export function refusalReasonFor(rawMessage, coded) {
|
|
486
|
-
if (coded !== undefined)
|
|
492
|
+
if (coded !== undefined) {
|
|
487
493
|
return REASON_OF_CODE[coded.code];
|
|
494
|
+
}
|
|
488
495
|
const message = zodArrayAsSentence(rawMessage);
|
|
489
|
-
if (isUnissuedRefRefusal(message))
|
|
496
|
+
if (isUnissuedRefRefusal(message)) {
|
|
490
497
|
return RefusalReason.BAD_ARGS;
|
|
498
|
+
}
|
|
491
499
|
// The no-session diagnosis inspected the machine and named the cause itself, so it never reaches
|
|
492
500
|
// the recovery table. It is the single largest refusal there is; missing it would gut the metric.
|
|
493
|
-
if (message.includes(SELF_RECOVERING_MARKER))
|
|
501
|
+
if (message.includes(SELF_RECOVERING_MARKER)) {
|
|
494
502
|
return RefusalReason.NO_SESSION;
|
|
503
|
+
}
|
|
495
504
|
for (const rule of SELF_RECOVERING) {
|
|
496
|
-
if (rule.match.test(message))
|
|
505
|
+
if (rule.match.test(message)) {
|
|
497
506
|
return rule.reason;
|
|
507
|
+
}
|
|
498
508
|
}
|
|
499
509
|
// Same order as buildErrorPayload: a schema rejection is the agent's own call, whatever else in
|
|
500
510
|
// the message might also match.
|
|
501
|
-
if (ARGUMENT_REJECTION.test(message))
|
|
511
|
+
if (ARGUMENT_REJECTION.test(message)) {
|
|
502
512
|
return RefusalReason.BAD_ARGS;
|
|
503
|
-
|
|
513
|
+
}
|
|
514
|
+
if (INVALID_NAME_REJECTION.test(message)) {
|
|
504
515
|
return RefusalReason.BAD_ARGS;
|
|
516
|
+
}
|
|
505
517
|
const hint = recoveryFor(message);
|
|
506
518
|
return (hint === undefined ? undefined : REASON_BY_HINT.get(hint)) ?? RefusalReason.OTHER;
|
|
507
519
|
}
|
|
@@ -520,8 +532,9 @@ export function refusalReasonFor(rawMessage, coded) {
|
|
|
520
532
|
/** Max issues rendered — a union rejection can emit one per member, which is how the array became unreadable. */
|
|
521
533
|
const MAX_RENDERED_ISSUES = 3;
|
|
522
534
|
function isZodIssue(value) {
|
|
523
|
-
if (typeof value !== 'object' || null === value)
|
|
535
|
+
if (typeof value !== 'object' || null === value) {
|
|
524
536
|
return false;
|
|
537
|
+
}
|
|
525
538
|
const v = value;
|
|
526
539
|
return 'string' === typeof v['code'] && 'string' === typeof v['message'];
|
|
527
540
|
}
|
|
@@ -548,8 +561,9 @@ function issueClause(issue) {
|
|
|
548
561
|
*/
|
|
549
562
|
function zodArrayAsSentence(message) {
|
|
550
563
|
const text = message.trim();
|
|
551
|
-
if (!text.startsWith('['))
|
|
564
|
+
if (!text.startsWith('[')) {
|
|
552
565
|
return message;
|
|
566
|
+
}
|
|
553
567
|
let parsed;
|
|
554
568
|
try {
|
|
555
569
|
parsed = JSON.parse(text);
|
|
@@ -557,8 +571,9 @@ function zodArrayAsSentence(message) {
|
|
|
557
571
|
catch {
|
|
558
572
|
return message; // not JSON at all — e.g. prose that happens to open with a bracket
|
|
559
573
|
}
|
|
560
|
-
if (!Array.isArray(parsed) || 0 === parsed.length || !parsed.every(isZodIssue))
|
|
574
|
+
if (!Array.isArray(parsed) || 0 === parsed.length || !parsed.every(isZodIssue)) {
|
|
561
575
|
return message;
|
|
576
|
+
}
|
|
562
577
|
const shown = parsed.slice(0, MAX_RENDERED_ISSUES).map(issueClause).join('; ');
|
|
563
578
|
const rest = parsed.length - MAX_RENDERED_ISSUES;
|
|
564
579
|
return rest > 0 ? `${shown} (and ${String(rest)} more)` : shown;
|
|
@@ -613,8 +628,9 @@ const REASON_OF_CODE = {
|
|
|
613
628
|
* its sentence: the not-linked refusal was matched by its prose, and the prose was rewritten.
|
|
614
629
|
*/
|
|
615
630
|
export function codedRefusalOf(error) {
|
|
616
|
-
if (!(error instanceof Error))
|
|
631
|
+
if (!(error instanceof Error)) {
|
|
617
632
|
return undefined;
|
|
633
|
+
}
|
|
618
634
|
const parsed = CodedRefusalSchema.safeParse({
|
|
619
635
|
code: 'code' in error ? error.code : undefined,
|
|
620
636
|
next: 'next' in error ? error.next : undefined,
|
|
@@ -631,24 +647,29 @@ export function buildErrorPayload(rawMessage, coded) {
|
|
|
631
647
|
// leave an unparseable fragment as the agent's error text — the worst of both shapes.
|
|
632
648
|
const message = capMessage(zodArrayAsSentence(rawMessage));
|
|
633
649
|
// A coded refusal says who does what next itself: no hint beside it, and no bug-report ask.
|
|
634
|
-
if (coded !== undefined)
|
|
650
|
+
if (coded !== undefined) {
|
|
635
651
|
return { error: message, code: coded.code, next: coded.next };
|
|
636
|
-
|
|
652
|
+
}
|
|
653
|
+
if (isUnissuedRefRefusal(message)) {
|
|
637
654
|
return { error: message, recovery: RECOVERY.UNISSUED_REF };
|
|
655
|
+
}
|
|
638
656
|
// A self-diagnosing message gets NEITHER a generic recovery nor the feedback ask. It already
|
|
639
657
|
// inspected the machine and named the cause; a second, contradictory hint is noise, and inviting a
|
|
640
658
|
// bug report about a condition Reticle diagnosed itself is exactly backwards.
|
|
641
|
-
if (isSelfRecovering(message))
|
|
659
|
+
if (isSelfRecovering(message)) {
|
|
642
660
|
return { error: message };
|
|
661
|
+
}
|
|
643
662
|
// An argument rejection is the agent's to fix, not a Reticle defect. Reported from the field: a
|
|
644
663
|
// clean `unrecognized_keys: ["value"]` came back wrapped in "may be a defect in Reticle", which
|
|
645
664
|
// spends the agent's turn and fills the feedback channel with reports about malformed calls.
|
|
646
|
-
if (ARGUMENT_REJECTION.test(message))
|
|
665
|
+
if (ARGUMENT_REJECTION.test(message)) {
|
|
647
666
|
return { error: message, recovery: ARGUMENT_RECOVERY };
|
|
667
|
+
}
|
|
648
668
|
// Reticle's own name validators rejecting a caller's value: an argument rejection that the schema
|
|
649
669
|
// cannot express (the shape is a runtime pattern, not a type), so it needs its own arm.
|
|
650
|
-
if (INVALID_NAME_REJECTION.test(message))
|
|
670
|
+
if (INVALID_NAME_REJECTION.test(message)) {
|
|
651
671
|
return { error: message, recovery: RECOVERY.INVALID_NAME };
|
|
672
|
+
}
|
|
652
673
|
const recovery = recoveryFor(message);
|
|
653
674
|
return recovery !== undefined
|
|
654
675
|
? { error: message, recovery }
|
|
@@ -17,6 +17,7 @@ import { endsRun, noteToolResult, runReportField } from './run-report.js';
|
|
|
17
17
|
import { runLedger } from '../../judgement/runs/run-ledger.js';
|
|
18
18
|
import { readAppNotes } from './agent/app-notes.js';
|
|
19
19
|
import { forTheAgent } from './agent/for-the-agent.js';
|
|
20
|
+
import { inRepeatStreak, withHintOn } from './repeat-hint.js';
|
|
20
21
|
import { feedbackAlreadyAsked, noteFeedbackAsked } from './agent/notices.js';
|
|
21
22
|
import { SKEW_SHORT_WARNING, SKEW_VERDICT_WARNING } from '../../portal/session/session-health.js';
|
|
22
23
|
import { verifyNextBaton, SUPPRESS_VERIFY_NEXT_ENV } from './verify-next-baton.js';
|
|
@@ -46,6 +47,7 @@ import { takeStepShot } from '../../features/visual/step-shots.js';
|
|
|
46
47
|
import { defectForToolResult, deltaForToolResult, impactSnapshot, initImpact, recordImpact, } from '../../memory/impact/impact-recorder.js';
|
|
47
48
|
import { FrictionKind, frictionOf, inviteFor } from './feedback-invite.js';
|
|
48
49
|
import { VACUOUS_BECAUSE } from '@reticlehq/engine/evidence/verified.js';
|
|
50
|
+
import { NEXT_STEP_FIELD } from './empty-session-list.js';
|
|
49
51
|
/**
|
|
50
52
|
* Emit `verification_completed` when a verification tool produced a verdict.
|
|
51
53
|
*
|
|
@@ -134,11 +136,11 @@ function reportBugsFound(toolName, result, sessionId) {
|
|
|
134
136
|
* tool's name, and then hands the same failure back out to be reported a second time as
|
|
135
137
|
* `reticle_run`. A count that doubles because of HOW a tool was reached is not a measurement.
|
|
136
138
|
*/
|
|
137
|
-
function reportRefusal(toolName, message, error) {
|
|
139
|
+
function reportRefusal(toolName, args, message, error) {
|
|
138
140
|
if (toolName === ReticleTool.RUN) {
|
|
139
141
|
return;
|
|
140
142
|
}
|
|
141
|
-
reportToolRefused(toolName, refusalReasonFor(message, codedRefusalOf(error)));
|
|
143
|
+
reportToolRefused(toolName, refusalReasonFor(message, codedRefusalOf(error)), args);
|
|
142
144
|
}
|
|
143
145
|
/**
|
|
144
146
|
* Which friction this result represents, counting the invitation if there is one.
|
|
@@ -256,25 +258,20 @@ export async function runTool(tool, deps, given, call) {
|
|
|
256
258
|
// The project of the session a call ends, read while the session still exists: releasing a
|
|
257
259
|
// tab removes it, and an unreadable project lost an unlinked user's cloud line.
|
|
258
260
|
const endedIn = agentCall && ends ? safeRoot(() => sessionRoot(deps, sessionIdFromArgs(args))) : undefined;
|
|
259
|
-
|
|
261
|
+
// `reticle_run` is counted once, as the tool it reached; never another agent's call (rule 8).
|
|
262
|
+
const owner = currentDrivenBy() === undefined && ReticleTool.RUN !== tool.name;
|
|
263
|
+
const { raw, hint } = await inRepeatStreak(owner ? deps.attachId : undefined, tool.name, args, () => loggedTool(tool, deps, args, call));
|
|
260
264
|
const attach = currentDrivenBy() === undefined ? deps.attachId : undefined;
|
|
261
265
|
const root = attach === undefined
|
|
262
266
|
? undefined
|
|
263
267
|
: safeRoot(() => sessionRoot(deps, sessionIdFromArgs(args)));
|
|
264
268
|
// The app notes before this call, so a note it learns counts as memory written.
|
|
265
269
|
const notesBefore = agentCall && root !== undefined ? readAppNotes(root) : [];
|
|
266
|
-
const answer = forTheAgent({
|
|
267
|
-
name: tool.name,
|
|
268
|
-
args,
|
|
269
|
-
result: raw,
|
|
270
|
-
attach,
|
|
271
|
-
root,
|
|
272
|
-
fold: NOTICE_FOLD,
|
|
273
|
-
});
|
|
270
|
+
const answer = withHintOn(forTheAgent({ name: tool.name, args, result: raw, attach, root, fold: NOTICE_FOLD }), hint);
|
|
274
271
|
if (!agentCall) {
|
|
275
272
|
return answer;
|
|
276
273
|
}
|
|
277
|
-
runLedger.after(deps.now());
|
|
274
|
+
runLedger.after(deps.now(), deps.attachId);
|
|
278
275
|
noteToolResult(runLedger, tool.name, args, raw);
|
|
279
276
|
const notesAfter = root === undefined ? [] : readAppNotes(root);
|
|
280
277
|
runLedger.memoryWritten(notesAfter.filter((note) => !notesBefore.includes(note)).length);
|
|
@@ -282,7 +279,10 @@ export async function runTool(tool, deps, given, call) {
|
|
|
282
279
|
if (!ends || null === answer || 'object' !== typeof answer) {
|
|
283
280
|
return answer;
|
|
284
281
|
}
|
|
285
|
-
return {
|
|
282
|
+
return {
|
|
283
|
+
...answer,
|
|
284
|
+
...runReportField(runLedger, endedIn ?? root, process.env, deps.now(), deps.attachId),
|
|
285
|
+
};
|
|
286
286
|
}));
|
|
287
287
|
}
|
|
288
288
|
/** Advisories that become once-per-session notice lines. See agent/notices.ts. */
|
|
@@ -391,7 +391,7 @@ async function dispatchTool(tool, deps, args, call, seen = {}) {
|
|
|
391
391
|
'value — a selector or an id rather than a document, or a slice of the text you need.';
|
|
392
392
|
// Refused before the handler was ever entered, and still a refusal the agent has to recover
|
|
393
393
|
// from. Reported here rather than at the return below, which this branch never reaches.
|
|
394
|
-
reportRefusal(tool.name, error);
|
|
394
|
+
reportRefusal(tool.name, args, error);
|
|
395
395
|
return { error };
|
|
396
396
|
}
|
|
397
397
|
// One driver per tab. The Harness clicking through a page while the agent clicks too is two hands
|
|
@@ -399,7 +399,7 @@ async function dispatchTool(tool, deps, args, call, seen = {}) {
|
|
|
399
399
|
const busy = ACTION_TOOLS.has(tool.name) ? harnessDriving(deps, args) : undefined;
|
|
400
400
|
if (busy !== undefined) {
|
|
401
401
|
const error = msgHarnessHasTheTab(busy);
|
|
402
|
-
reportRefusal(tool.name, error);
|
|
402
|
+
reportRefusal(tool.name, args, error);
|
|
403
403
|
return { error };
|
|
404
404
|
}
|
|
405
405
|
// An ACTION is what gets abandoned. Counted here, against verifications, so "the agent drove the
|
|
@@ -448,7 +448,7 @@ async function dispatchTool(tool, deps, args, call, seen = {}) {
|
|
|
448
448
|
// before Playwright invents a closed page. ready:true leases still hit this path.
|
|
449
449
|
if (session?.versionSkew !== undefined && CDP_TOOLS.has(tool.name)) {
|
|
450
450
|
const message = session.versionSkew;
|
|
451
|
-
reportRefusal(tool.name, message);
|
|
451
|
+
reportRefusal(tool.name, args, message);
|
|
452
452
|
throw new Error(message);
|
|
453
453
|
}
|
|
454
454
|
// A ref that was handed out by a DIFFERENT tab, on a call that named no session. Refused here, at
|
|
@@ -466,7 +466,7 @@ async function dispatchTool(tool, deps, args, call, seen = {}) {
|
|
|
466
466
|
if (wrongTab !== undefined) {
|
|
467
467
|
// Refused before the handler was entered, so it is reported here — the same shape as the
|
|
468
468
|
// oversized-argument refusal above.
|
|
469
|
-
reportRefusal(tool.name, wrongTab);
|
|
469
|
+
reportRefusal(tool.name, args, wrongTab);
|
|
470
470
|
return { error: wrongTab };
|
|
471
471
|
}
|
|
472
472
|
}
|
|
@@ -516,7 +516,7 @@ async function dispatchTool(tool, deps, args, call, seen = {}) {
|
|
|
516
516
|
const rewritten = rewriteClosedAsSkew(error, skewText);
|
|
517
517
|
const toThrow = rewritten ?? error;
|
|
518
518
|
const message = toThrow instanceof Error ? toThrow.message : String(toThrow);
|
|
519
|
-
reportRefusal(tool.name, message, toThrow);
|
|
519
|
+
reportRefusal(tool.name, args, message, toThrow);
|
|
520
520
|
// The invitation on THIS path is the one `buildErrorPayload` attaches when it does not recognise
|
|
521
521
|
// the error, and it was never counted — so the denominator excluded the commonest friction there
|
|
522
522
|
// is. Gated on the payload actually carrying the ask, because a recognised error gets a recovery
|
|
@@ -580,7 +580,7 @@ async function dispatchTool(tool, deps, args, call, seen = {}) {
|
|
|
580
580
|
// project the daemon was started in.
|
|
581
581
|
session?.pushImpact?.(() => impactSnapshot(artifactRoot));
|
|
582
582
|
if (resultIsError(raw)) {
|
|
583
|
-
reportRefusal(tool.name, raw.error);
|
|
583
|
+
reportRefusal(tool.name, args, raw.error);
|
|
584
584
|
}
|
|
585
585
|
else {
|
|
586
586
|
noteToolServed();
|
|
@@ -660,6 +660,11 @@ async function dispatchTool(tool, deps, args, call, seen = {}) {
|
|
|
660
660
|
})
|
|
661
661
|
: undefined;
|
|
662
662
|
const platformLine = platform === undefined ? nudge : nudge === undefined ? platform : `${platform}\n${nudge}`;
|
|
663
|
+
// The first call of this connection, in an unwired app with nothing attached: the offer the
|
|
664
|
+
// plugin's SessionStart hook makes, which `/reload-plugins` never fires. Always taken, so a call
|
|
665
|
+
// that already said it (the opened tab's notice, an empty list's `next_step`) still uses the once.
|
|
666
|
+
const firstOffer = agentCall && deps.attachId !== undefined ? deps.firstSessionOffer?.(deps.attachId) : undefined;
|
|
667
|
+
const offer = opened === undefined && !(NEXT_STEP_FIELD in (answer ?? {})) ? firstOffer : undefined;
|
|
663
668
|
const drives = agentCall
|
|
664
669
|
? takeDriveNotes('string' === typeof answer?.['runId'] ? answer['runId'] : undefined)
|
|
665
670
|
: [];
|
|
@@ -675,7 +680,8 @@ async function dispatchTool(tool, deps, args, call, seen = {}) {
|
|
|
675
680
|
undelivered === undefined &&
|
|
676
681
|
friction === undefined &&
|
|
677
682
|
platformLine === undefined &&
|
|
678
|
-
next === undefined
|
|
683
|
+
next === undefined &&
|
|
684
|
+
offer === undefined
|
|
679
685
|
? raw
|
|
680
686
|
: {
|
|
681
687
|
// First, so a model that truncates a long result still reads it.
|
|
@@ -696,6 +702,7 @@ async function dispatchTool(tool, deps, args, call, seen = {}) {
|
|
|
696
702
|
...(update !== undefined ? { [EnvelopeKey.UPDATE_AVAILABLE]: update } : {}),
|
|
697
703
|
...(skew !== undefined ? { [EnvelopeKey.VERSION_SKEW]: skew } : {}),
|
|
698
704
|
...(platformLine !== undefined ? { [EnvelopeKey.PLATFORM]: platformLine } : {}),
|
|
705
|
+
...(offer === undefined ? {} : { [EnvelopeKey.FIRST_SESSION_OFFER]: offer }),
|
|
699
706
|
...(0 === drives.length ? {} : { [EnvelopeKey.HARNESS]: drives.join('\n') }),
|
|
700
707
|
...(notes === undefined ? {} : { [EnvelopeKey.CONTROL]: notes }),
|
|
701
708
|
...(undelivered !== undefined
|
|
@@ -56,6 +56,13 @@ export interface LeaseEvidence {
|
|
|
56
56
|
*/
|
|
57
57
|
dialledUrl?: string;
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* The last sentence of every not-connected hint. It is true for an MCP caller, which keeps the
|
|
61
|
+
* lease. The CLI releases the lease itself, and puts `LEASE_RELEASED_SENTENCE` in its place.
|
|
62
|
+
*/
|
|
63
|
+
export declare const LEASE_KEPT_SENTENCE = "The tab stays leased. Release it with reticle_lease{action:\"release\"}.";
|
|
64
|
+
/** The sentence for a lease that is already released. */
|
|
65
|
+
export declare const LEASE_RELEASED_SENTENCE = "The lease is released.";
|
|
59
66
|
export declare function leaseNotConnectedHint(url: string, port: number, evidence?: LeaseEvidence): string;
|
|
60
67
|
/**
|
|
61
68
|
* The leased page was still loading when the wait ran out, so nothing has failed yet: a slow first
|
|
@@ -37,7 +37,14 @@ const REAL_CAUSES = 'Causes that produce exactly this and are NOT a port problem
|
|
|
37
37
|
const NUXT_FIRST = 'This project is Nuxt: the most likely cause by a distance is a dev server that predates the ' +
|
|
38
38
|
'Reticle plugin, because Nuxt does NOT register a newly added plugin on HMR. Restart `nuxt dev` ' +
|
|
39
39
|
'and re-acquire before investigating anything else.';
|
|
40
|
-
|
|
40
|
+
/**
|
|
41
|
+
* The last sentence of every not-connected hint. It is true for an MCP caller, which keeps the
|
|
42
|
+
* lease. The CLI releases the lease itself, and puts `LEASE_RELEASED_SENTENCE` in its place.
|
|
43
|
+
*/
|
|
44
|
+
export const LEASE_KEPT_SENTENCE = 'The tab stays leased. Release it with reticle_lease{action:"release"}.';
|
|
45
|
+
const RELEASE = LEASE_KEPT_SENTENCE;
|
|
46
|
+
/** The sentence for a lease that is already released. */
|
|
47
|
+
export const LEASE_RELEASED_SENTENCE = 'The lease is released.';
|
|
41
48
|
const PRODUCTION_STUB = ' If this is a production build (`vite build`, `next build`, `next start`, or a deployed site), it strips ' +
|
|
42
49
|
'Reticle or replaces it with an inert stub by design; point the lease at the dev server instead.';
|
|
43
50
|
function isRemoteUrl(url) {
|
|
@@ -168,8 +175,8 @@ export function leaseStillLoadingHint(url, waitedMs) {
|
|
|
168
175
|
/** The hint for a lease whose zero-install reader did not run (#1701). The lease is already released. */
|
|
169
176
|
export function leaseReaderFailedHint(url, failure) {
|
|
170
177
|
return failure.reason === LeaseNotReadyReason.NOT_HTML_DOCUMENT
|
|
171
|
-
? `${url} is not an HTML page, so no SDK can connect to it.
|
|
178
|
+
? `${url} is not an HTML page, so no SDK can connect to it. ${LEASE_RELEASED_SENTENCE} ` +
|
|
172
179
|
'Acquire an HTML page of the app instead.'
|
|
173
|
-
: `The zero-install reader failed on ${url}: ${failure.cause}.
|
|
180
|
+
: `The zero-install reader failed on ${url}: ${failure.cause}. ${LEASE_RELEASED_SENTENCE} ` +
|
|
174
181
|
'If the tab closed during setup, acquire again.';
|
|
175
182
|
}
|
|
@@ -43,6 +43,27 @@ export declare function notReadyReason(answered: boolean, session: unknown, fail
|
|
|
43
43
|
* failure than the one being fixed.
|
|
44
44
|
*/
|
|
45
45
|
export declare function probeLeaseAlive(session: ProbeableSession | undefined, timeoutMs?: number): Promise<boolean>;
|
|
46
|
+
/**
|
|
47
|
+
* The longest `reticle_lease { action: "acquire" }` waits for its tab to be ready, when the caller
|
|
48
|
+
* gives no `timeout_ms`. It is the longest wait before this bound existed: the connect wait, the
|
|
49
|
+
* wait after the reader, and the loading grace rounds. So no acquire that was ready before is now
|
|
50
|
+
* refused.
|
|
51
|
+
*/
|
|
52
|
+
export declare const LEASE_READY_TIMEOUT_MS: number;
|
|
53
|
+
/**
|
|
54
|
+
* A connect wait for `connectOrInject` that ends at the deadline. Each wait is one connect wait at
|
|
55
|
+
* most, so the reader still goes in at the same time. A wait that starts after the deadline looks
|
|
56
|
+
* once and does not sleep.
|
|
57
|
+
*/
|
|
58
|
+
export declare function connectWaitUntil(deadline: number, now: () => number, sleeper?: (ms: number) => Promise<void>): (isConnected: () => boolean) => Promise<boolean>;
|
|
59
|
+
/**
|
|
60
|
+
* Wait until the leased tab is registered, answers a probe and is visible, or the deadline passes.
|
|
61
|
+
*
|
|
62
|
+
* A row in the sessions map is not proof that the next call works (#1650): the tab can be attached
|
|
63
|
+
* and answer nothing. So `ready` waits for a reply. Returns the reason at the deadline, or
|
|
64
|
+
* `undefined` when the tab is ready. It always looks once, also when the deadline has passed.
|
|
65
|
+
*/
|
|
66
|
+
export declare function awaitLeaseAnswering(registered: () => ProbeableSession | undefined, deadline: number, now: () => number, sleeper?: (ms: number) => Promise<void>): Promise<LeaseNotReadyReason | undefined>;
|
|
46
67
|
/**
|
|
47
68
|
* Poll until the leased tab's SDK has actually registered on the bridge, so the sessionId we return
|
|
48
69
|
* is immediately usable (the page navigates first, then connects a moment later — without this wait
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { LeaseNotReadyReason, ReticleCommand, RETICLE_URL_PARAM } from '@reticlehq/core';
|
|
8
8
|
import { NotHtmlDocumentError } from '../../portal/pool/zero-install.js';
|
|
9
|
+
import { waitForReconnect } from '../../portal/session/session-reconnect.js';
|
|
9
10
|
/**
|
|
10
11
|
* How long a liveness probe waits for the tab to say anything at all.
|
|
11
12
|
*
|
|
@@ -74,6 +75,52 @@ const LEASE_READY_POLL_MS = 100;
|
|
|
74
75
|
/** Extra readiness waits allowed while the leased page is still loading (each one is ~10 s). */
|
|
75
76
|
const LEASE_LOADING_GRACE_ROUNDS = 3;
|
|
76
77
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
78
|
+
/** One connect wait: how long a tab gets to dial in before the zero-install reader goes in. */
|
|
79
|
+
const LEASE_CONNECT_WAIT_MS = LEASE_READY_ATTEMPTS * LEASE_READY_POLL_MS;
|
|
80
|
+
/**
|
|
81
|
+
* The longest `reticle_lease { action: "acquire" }` waits for its tab to be ready, when the caller
|
|
82
|
+
* gives no `timeout_ms`. It is the longest wait before this bound existed: the connect wait, the
|
|
83
|
+
* wait after the reader, and the loading grace rounds. So no acquire that was ready before is now
|
|
84
|
+
* refused.
|
|
85
|
+
*/
|
|
86
|
+
export const LEASE_READY_TIMEOUT_MS = LEASE_CONNECT_WAIT_MS * (2 + LEASE_LOADING_GRACE_ROUNDS);
|
|
87
|
+
/** `waitForReconnect` waits for `current` to differ from `previous`: these two always differ. */
|
|
88
|
+
const NOT_REGISTERED = {};
|
|
89
|
+
const REGISTERED = {};
|
|
90
|
+
/**
|
|
91
|
+
* A connect wait for `connectOrInject` that ends at the deadline. Each wait is one connect wait at
|
|
92
|
+
* most, so the reader still goes in at the same time. A wait that starts after the deadline looks
|
|
93
|
+
* once and does not sleep.
|
|
94
|
+
*/
|
|
95
|
+
export function connectWaitUntil(deadline, now, sleeper = sleep) {
|
|
96
|
+
return (isConnected) => waitForReconnect({
|
|
97
|
+
current: () => (isConnected() ? REGISTERED : undefined),
|
|
98
|
+
previous: NOT_REGISTERED,
|
|
99
|
+
timeoutMs: Math.min(LEASE_CONNECT_WAIT_MS, Math.max(0, deadline - now())),
|
|
100
|
+
now,
|
|
101
|
+
sleep: sleeper,
|
|
102
|
+
pollMs: LEASE_READY_POLL_MS,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Wait until the leased tab is registered, answers a probe and is visible, or the deadline passes.
|
|
107
|
+
*
|
|
108
|
+
* A row in the sessions map is not proof that the next call works (#1650): the tab can be attached
|
|
109
|
+
* and answer nothing. So `ready` waits for a reply. Returns the reason at the deadline, or
|
|
110
|
+
* `undefined` when the tab is ready. It always looks once, also when the deadline has passed.
|
|
111
|
+
*/
|
|
112
|
+
export async function awaitLeaseAnswering(registered, deadline, now, sleeper = sleep) {
|
|
113
|
+
for (;;) {
|
|
114
|
+
const session = registered();
|
|
115
|
+
const reason = session === undefined
|
|
116
|
+
? LeaseNotReadyReason.SDK_STOPPED_ANSWERING
|
|
117
|
+
: notReadyReason(await probeLeaseAlive(session), session);
|
|
118
|
+
if (reason === undefined || now() >= deadline) {
|
|
119
|
+
return reason;
|
|
120
|
+
}
|
|
121
|
+
await sleeper(LEASE_READY_POLL_MS);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
77
124
|
/**
|
|
78
125
|
* Poll until the leased tab's SDK has actually registered on the bridge, so the sessionId we return
|
|
79
126
|
* is immediately usable (the page navigates first, then connects a moment later — without this wait
|
|
@@ -36,6 +36,8 @@ export declare const NAV_TIMEOUT_ADVICE: string;
|
|
|
36
36
|
* cases and leads with the retry.
|
|
37
37
|
*/
|
|
38
38
|
export declare function navFailureMessage(url: string, err: unknown, seed?: SeedStorage): string;
|
|
39
|
+
/** Whether a path is a sign-in page, by its name: `/login`, `/signin`, `/auth/login` and similar. */
|
|
40
|
+
export declare const isAuthPath: (pathname: string) => boolean;
|
|
39
41
|
/**
|
|
40
42
|
* Determine if a required seedStorage application precondition failed.
|
|
41
43
|
*
|
|
@@ -127,6 +127,8 @@ export function navFailureMessage(url, err, seed) {
|
|
|
127
127
|
: `within ${String(Number(budgetMs) / 1000)} s`;
|
|
128
128
|
return `could not open ${url} — ${NAV_TIMEOUT_LEAD} ${within} (${reason}). ${NAV_TIMEOUT_ADVICE}`;
|
|
129
129
|
}
|
|
130
|
+
/** Whether a path is a sign-in page, by its name: `/login`, `/signin`, `/auth/login` and similar. */
|
|
131
|
+
export const isAuthPath = (pathname) => /(?:^|\/)(?:login|signin|sign-in|auth\/login|auth\/signin|session\/new)(?:$|\/)/i.test(pathname);
|
|
130
132
|
/**
|
|
131
133
|
* Determine if a required seedStorage application precondition failed.
|
|
132
134
|
*
|
|
@@ -159,7 +161,6 @@ export function evaluateSeedPrecondition(requestedUrl, actualUrl, seed, navStatu
|
|
|
159
161
|
return `seeded storage precondition not established: cross-origin redirect from ${reqParsed.origin} to ${actParsed.origin} skipped origin-scoped storage injection`;
|
|
160
162
|
}
|
|
161
163
|
// Clear redirect to an authentication/login endpoint when the requested URL was not a login page
|
|
162
|
-
const isAuthPath = (pathname) => /(?:^|\/)(?:login|signin|sign-in|auth\/login|auth\/signin|session\/new)(?:$|\/)/i.test(pathname);
|
|
163
164
|
if (!isAuthPath(reqParsed.pathname) && isAuthPath(actParsed.pathname)) {
|
|
164
165
|
return `seeded authentication precondition not established: redirected from ${reqParsed.pathname} to login page (${actParsed.pathname})`;
|
|
165
166
|
}
|