@mjasnikovs/pi-task 0.40.34 → 0.40.36

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.
@@ -113,9 +113,9 @@ export interface DeepSessionFacts {
113
113
  * The three request-shaped facts, computed from the log and from nothing else. The
114
114
  * driver records `sessionRequests` and calls this; the values are exactly what the
115
115
  * pre-log driver computed by filtering the same map (the sign-in request is the
116
- * first same-origin non-GET issued at or after the submit; the data requests are the
117
- * same-origin
118
- * XHR/fetch issued at or after the submit, the sign-in request itself excluded).
116
+ * first same-origin non-GET issued at or after the submit; the data requests are
117
+ * the same-origin XHR/fetch issued at or after the sign-in request, that request
118
+ * itself excluded).
119
119
  */
120
120
  export declare function deriveLegacyFacts(log: SessionRequest[]): Pick<DeepSessionFacts, 'authRequest' | 'postAuthDataAttempted' | 'postAuthData2xx'>;
121
121
  /**
@@ -202,9 +202,9 @@ export function pinnedLocalPort(vars) {
202
202
  * The three request-shaped facts, computed from the log and from nothing else. The
203
203
  * driver records `sessionRequests` and calls this; the values are exactly what the
204
204
  * pre-log driver computed by filtering the same map (the sign-in request is the
205
- * first same-origin non-GET issued at or after the submit; the data requests are the
206
- * same-origin
207
- * XHR/fetch issued at or after the submit, the sign-in request itself excluded).
205
+ * first same-origin non-GET issued at or after the submit; the data requests are
206
+ * the same-origin XHR/fetch issued at or after the sign-in request, that request
207
+ * itself excluded).
208
208
  */
209
209
  export function deriveLegacyFacts(log) {
210
210
  const auth = log.find(r => r.phase === 'auth') ?? null;
@@ -291,8 +291,12 @@ export function judgeDeepSession(f) {
291
291
  // `GET /*` → index.html fallback returns 200 for a route it does not have, so
292
292
  // the status is healthy and the body is the app shell. A document navigation
293
293
  // answered with HTML is normal; an XHR asking for data and getting HTML is a
294
- // call that reached nothing.
295
- const swallowed = (f.sessionRequests ?? []).find(r => r.initiator === 'xhr' && (r.mimeType ?? '').startsWith('text/html'));
294
+ // call that reached nothing. The sign-in request is exempt: it keeps the first
295
+ // hop but carries the LAST hop's mime, so a fetch login that 302s to a page
296
+ // reads as HTML here while being a sign-in that worked.
297
+ const swallowed = (f.sessionRequests ?? []).find(r => r.initiator === 'xhr'
298
+ && r.phase !== 'auth'
299
+ && (r.mimeType ?? '').startsWith('text/html'));
296
300
  if (swallowed) {
297
301
  return {
298
302
  outcome: 'fail',
@@ -655,10 +659,15 @@ export async function driveSession(cdp, { url, credentials, judge, quietMs }) {
655
659
  // judges it is the chain's last, so the first hop stays and the eventual
656
660
  // response lands on it. Overwriting reads a POST that 302s as a GET, and
657
661
  // then no sign-in request is ever found.
658
- if (p.redirectResponse === undefined || !requests.has(id)) {
659
- const req = p.request;
662
+ const req = p.request;
663
+ const existing = requests.get(id);
664
+ if (p.redirectResponse !== undefined && existing) {
665
+ existing.finalUrl = String(req?.url ?? existing.finalUrl);
666
+ }
667
+ else {
660
668
  requests.set(id, {
661
669
  url: String(req?.url ?? ''),
670
+ finalUrl: String(req?.url ?? ''),
662
671
  method: String(req?.method ?? 'GET'),
663
672
  type: String(p.type ?? ''),
664
673
  status: null,
@@ -702,15 +711,15 @@ export async function driveSession(cdp, { url, credentials, judge, quietMs }) {
702
711
  const before = await evaluate(INSPECT_EXPR);
703
712
  if (!before)
704
713
  throw new Error('the page could not be inspected');
705
- const sameOrigin = (r) => r.url.startsWith(`${origin}/`) || r.url === origin;
714
+ const sameOrigin = (r) => r.finalUrl.startsWith(`${origin}/`) || r.finalUrl === origin;
706
715
  const isData = (r) => r.type === 'XHR' || r.type === 'Fetch';
707
716
  const foreignOriginFailures = () => {
708
717
  const out = new Set();
709
718
  for (const r of requests.values()) {
710
- if (sameOrigin(r) || !r.failed || !r.url.startsWith('http'))
719
+ if (sameOrigin(r) || !r.failed || !r.finalUrl.startsWith('http'))
711
720
  continue;
712
721
  try {
713
- out.add(new URL(r.url).origin);
722
+ out.add(new URL(r.finalUrl).origin);
714
723
  }
715
724
  catch {
716
725
  // unparseable url — nothing to name
@@ -721,10 +730,11 @@ export async function driveSession(cdp, { url, credentials, judge, quietMs }) {
721
730
  const initiatorOf = (r) => isData(r) ? 'xhr'
722
731
  : r.type === 'Document' ? 'document'
723
732
  : 'other';
724
- /** The same-origin request log, phased against the submit. `postSeq` is the
725
- * first `seq` the submit could issue — Infinity while no submit has happened —
726
- * so before a submit every request so far is 'pre'; the sign-in request sits at
727
- * or after the boundary but is 'auth', not 'post'. */
733
+ /** The same-origin request log. `postSeq` is where the authenticated half
734
+ * begins — Infinity while no submit has happened — so everything before it is
735
+ * 'pre'; the sign-in request itself is 'auth', not 'post'. The boundary is the
736
+ * sign-in request, not the submit: a CSRF token or a beacon the submit fires
737
+ * BEFORE the login is not evidence about the authenticated client. */
728
738
  const sessionLog = (authId, postSeq) => [...requests]
729
739
  .filter(([, r]) => sameOrigin(r))
730
740
  .map(([id, r]) => ({
@@ -755,6 +765,7 @@ export async function driveSession(cdp, { url, credentials, judge, quietMs }) {
755
765
  const unsubmitted = (over) => facts(sessionLog(null, Number.POSITIVE_INFINITY), over);
756
766
  if (!before.hasPassword || credentials === null)
757
767
  return judge(unsubmitted({}));
768
+ const fillSeq = nextSeq;
758
769
  const filled = await evaluate(fillExpr(credentials.identifier, credentials.password));
759
770
  if (!filled?.ok)
760
771
  return judge(unsubmitted({ submitted: false }));
@@ -780,6 +791,18 @@ export async function driveSession(cdp, { url, credentials, judge, quietMs }) {
780
791
  break;
781
792
  }
782
793
  }
794
+ // Nothing after the submit: a form that submits from the fill's own input
795
+ // events already sent it. Only a NAVIGATION qualifies here — a form submission
796
+ // is one, and the background traffic the fill races (beacons, telemetry) is
797
+ // XHR or fetch, never a document.
798
+ if (authId === null) {
799
+ for (const [id, r] of requests) {
800
+ if (r.seq >= fillSeq && sameOrigin(r) && r.method !== 'GET' && r.type === 'Document') {
801
+ authId = id;
802
+ break;
803
+ }
804
+ }
805
+ }
783
806
  const authReq = authId === null ? null : requests.get(authId);
784
807
  const now = await evaluate(INSPECT_EXPR);
785
808
  const domJudgment = judgeRenderedDom(now?.html ?? '');
@@ -801,7 +824,7 @@ export async function driveSession(cdp, { url, credentials, judge, quietMs }) {
801
824
  lastActivity = Date.now();
802
825
  await settle(() => lastActivity, RE_NAV_CAP_MS, quietMs);
803
826
  }
804
- return judge(facts(sessionLog(authId, submitSeq), {
827
+ return judge(facts(sessionLog(authId, authReq?.seq ?? submitSeq), {
805
828
  submitted: true,
806
829
  foreignOriginFailures: foreignOriginFailures(),
807
830
  leftAuthWall,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mjasnikovs/pi-task",
3
- "version": "0.40.34",
3
+ "version": "0.40.36",
4
4
  "description": "Deterministic task planning and spec-orchestration for local models — crash-safe /task pipelines with verify/enforce gates, a real-time remote web view, and web/docs/fetch/worker subagent tools.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",